diff --git a/.eslintrc.json b/.eslintrc.json index e72d576a..71d33df8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -131,6 +131,11 @@ { "selector": "typeLike", "format": ["PascalCase"] }, { "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] } ], + "@typescript-eslint/no-confusing-void-expression": [ + "warn", + { "ignoreArrowShorthand": true } + ], + "@typescript-eslint/no-useless-constructor": "warn", "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/type-annotation-spacing": "warn", "@typescript-eslint/quotes": [ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd465c01..2d3808c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,6 @@ jobs: ./addons/xterm-addon-canvas/out-test/* \ ./addons/xterm-addon-fit/out/* \ ./addons/xterm-addon-fit/out-test/* \ - ./addons/xterm-addon-image/inwasm-builds/out/* \ ./addons/xterm-addon-image/out/* \ ./addons/xterm-addon-image/out-test/* \ ./addons/xterm-addon-ligatures/out/* \ @@ -73,6 +72,39 @@ jobs: - name: Lint API run: yarn lint-api + test-unit-coverage: + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + - name: Install dependencies + run: | + yarn --frozen-lockfile + yarn install-addons + - uses: actions/download-artifact@v3 + with: + name: build-artifacts + - name: Unzip artifacts (Linux, macOS) + if: runner.os != 'Windows' + run: unzip -o compressed-build.zip + - name: Unzip artifacts (Windows) + if: runner.os == 'Windows' + run: 7z x compressed-build.zip -aoa -o${{ github.workspace }} + - name: Print directory structure + run: ls -R + - name: Unit test coverage + run: | + yarn test-unit-coverage --forbid-only + EXIT_CODE=$? + ./node_modules/.bin/nyc report --reporter=cobertura + exit $EXIT_CODE + test-unit-parallel: timeout-minutes: 20 strategy: diff --git a/.vscode/settings.json b/.vscode/settings.json index 07d643c6..af3cc208 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { + "files.associations": { + ".eslintrc.json.typings": "jsonc" + }, "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", "mochaExplorer.envPath": ".mocha.env", diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ff29bbf1..8c199336 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,24 +3,24 @@ * @license MIT */ +import { ReadonlyColorSet } from 'browser/Types'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; +import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; -import { ReadonlyColorSet } from 'browser/Types'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; +import { isSafari } from 'common/Platform'; +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 { ICellData, IDisposable } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; -import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -import { Disposable, toDisposable } from 'common/Lifecycle'; -import { isSafari } from 'common/Platform'; -import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export abstract class BaseRenderLayer extends Disposable implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -37,7 +37,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer private _bitmapGenerator: (BitmapGenerator | undefined)[] = []; protected _charAtlas!: ITextureAtlas; - private _charAtlasDisposable?: IDisposable; + protected _charAtlasDisposable = this.register(new MutableDisposable()); public get canvas(): HTMLCanvasElement { return this._canvas; } public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; } @@ -74,7 +74,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } @@ -122,9 +121,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) { return; } - this._charAtlasDisposable?.dispose(); this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr); - this._charAtlasDisposable = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); + this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas); this._charAtlas.warmUp(); for (let i = 0; i < this._charAtlas.pages.length; i++) { this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas); diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 9f4ac896..c5174f86 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -7,7 +7,7 @@ import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStat import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable } from 'common/Lifecycle'; import { isFirefox } from 'common/Platform'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; @@ -26,7 +26,7 @@ interface ICursorState { export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; - private _cursorBlinkStateManager: CursorBlinkStateManager | undefined; + private _cursorBlinkStateManager: MutableDisposable = this.register(new MutableDisposable()); private _cell: ICellData = new CellData(); constructor( @@ -57,10 +57,6 @@ export class CursorRenderLayer extends BaseRenderLayer { }; this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this._handleOptionsChanged(); - this.register(toDisposable(() => { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; - })); } public resize(dim: IRenderDimensions): void { @@ -77,28 +73,27 @@ export class CursorRenderLayer extends BaseRenderLayer { public reset(): void { this._clearCursor(); - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); this._handleOptionsChanged(); } public handleBlur(): void { - this._cursorBlinkStateManager?.pause(); + this._cursorBlinkStateManager.value?.pause(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } public handleFocus(): void { - this._cursorBlinkStateManager?.resume(); + this._cursorBlinkStateManager.value?.resume(); this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y }); } private _handleOptionsChanged(): void { if (this._optionsService.rawOptions.cursorBlink) { - if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); + if (!this._cursorBlinkStateManager.value) { + this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService); } } else { - this._cursorBlinkStateManager?.dispose(); - this._cursorBlinkStateManager = undefined; + this._cursorBlinkStateManager.clear(); } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal @@ -106,14 +101,14 @@ export class CursorRenderLayer extends BaseRenderLayer { } public handleCursorMove(): void { - this._cursorBlinkStateManager?.restartBlinkAnimation(); + this._cursorBlinkStateManager.value?.restartBlinkAnimation(); } public handleGridChanged(startRow: number, endRow: number): void { - if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) { this._render(false); } else { - this._cursorBlinkStateManager.restartBlinkAnimation(); + this._cursorBlinkStateManager.value.restartBlinkAnimation(); } } @@ -159,7 +154,7 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Don't draw the cursor if it's blinking - if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) { this._clearCursor(); return; } diff --git a/addons/xterm-addon-fit/src/FitAddon.ts b/addons/xterm-addon-fit/src/FitAddon.ts index 6b3df6f0..f7245462 100644 --- a/addons/xterm-addon-fit/src/FitAddon.ts +++ b/addons/xterm-addon-fit/src/FitAddon.ts @@ -24,8 +24,6 @@ const MINIMUM_ROWS = 1; export class FitAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() {} - public activate(terminal: Terminal): void { this._terminal = terminal; } diff --git a/addons/xterm-addon-fit/test/FitAddon.api.ts b/addons/xterm-addon-fit/test/FitAddon.api.ts index ef4618ea..36b44211 100644 --- a/addons/xterm-addon-fit/test/FitAddon.api.ts +++ b/addons/xterm-addon-fit/test/FitAddon.api.ts @@ -41,9 +41,7 @@ describe('FitAddon', () => { }); describe('proposeDimensions', () => { - afterEach(async () => { - return await unloadFit(); - }); + afterEach(() => unloadFit()); it('default', async function(): Promise { await loadFit(); @@ -82,9 +80,7 @@ describe('FitAddon', () => { }); describe('fit', () => { - afterEach(async () => { - return await unloadFit(); - }); + afterEach(() => unloadFit()); it('default', async function(): Promise { await loadFit(); diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json deleted file mode 100644 index b4a652c8..00000000 --- a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/definition.json +++ /dev/null @@ -1 +0,0 @@ -{"def":{"name":"decode","type":0,"mode":1,"srctype":"Clang-C","imports":{"env":{"memory":{}}},"exports":{},"compile":{"switches":["-Wl,-z,stack-size=0","-Wl,--stack-first"]},"code":"\n typedef struct {\n unsigned int wp;\n unsigned int sp;\n unsigned int dp;\n unsigned int e_size;\n unsigned int b_size;\n unsigned int dummy[3];\n unsigned char data[0];\n } State;\n\n unsigned int *D0 = (unsigned int *) 1024;\n unsigned int *D1 = (unsigned int *) 2048;\n unsigned int *D2 = (unsigned int *) 3072;\n unsigned int *D3 = (unsigned int *) 4096;\n State *state = (State *) 5120;\n\n __attribute__((noinline)) int dec() {\n unsigned int nsp = (state->wp - 1) & ~3;\n unsigned char *src = state->data + state->sp;\n unsigned char *end = state->data + nsp;\n unsigned char *dst = state->data + state->dp;\n unsigned int accu;\n\n while (src < end) {\n if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1;\n *((unsigned int *) dst) = accu;\n dst += 3;\n src += 4;\n }\n state->sp = nsp;\n state->dp = dst - state->data;\n return 0;\n }\n\n int end() {\n int rem = state->wp - state->sp;\n if (rem > 4 && dec()) return 1;\n rem = state->wp - state->sp;\n if (rem < 2) return 1;\n\n unsigned char *src = state->data + state->sp;\n unsigned int accu = D0[src[0]] | D1[src[1]];\n int dp = 1;\n if (rem > 2 && src[2] != 61) {\n accu |= D2[src[2]];\n dp++;\n }\n if (rem == 4 && src[3] != 61) {\n accu |= D3[src[3]];\n dp++;\n }\n if (accu >> 24) return 1;\n *((unsigned int *) (state->data + state->dp)) = accu;\n state->dp += dp;\n return state->dp != state->b_size;\n }\n "},"memorySettings":{"descriptor":{"initial":1,"shared":false},"mode":"imported"},"srcDef":"{\n name: 'decode',\n type: 0 /* OutputType.INSTANCE */,\n mode: 1 /* OutputMode.SYNC */,\n srctype: 'Clang-C',\n imports: {\n env: { memory: new WebAssembly.Memory({ initial: 1 }) }\n },\n exports: {\n dec: () => 0,\n end: () => 0\n },\n compile: {\n switches: ['-Wl,-z,stack-size=0', '-Wl,--stack-first']\n },\n code: `\n typedef struct {\n unsigned int wp;\n unsigned int sp;\n unsigned int dp;\n unsigned int e_size;\n unsigned int b_size;\n unsigned int dummy[3];\n unsigned char data[0];\n } State;\n\n unsigned int *D0 = (unsigned int *) ${256 /* P32.D0 */ * 4};\n unsigned int *D1 = (unsigned int *) ${512 /* P32.D1 */ * 4};\n unsigned int *D2 = (unsigned int *) ${768 /* P32.D2 */ * 4};\n unsigned int *D3 = (unsigned int *) ${1024 /* P32.D3 */ * 4};\n State *state = (State *) ${1280 /* P32.STATE */ * 4};\n\n __attribute__((noinline)) int dec() {\n unsigned int nsp = (state->wp - 1) & ~3;\n unsigned char *src = state->data + state->sp;\n unsigned char *end = state->data + nsp;\n unsigned char *dst = state->data + state->dp;\n unsigned int accu;\n\n while (src < end) {\n if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1;\n *((unsigned int *) dst) = accu;\n dst += 3;\n src += 4;\n }\n state->sp = nsp;\n state->dp = dst - state->data;\n return 0;\n }\n\n int end() {\n int rem = state->wp - state->sp;\n if (rem > 4 && dec()) return 1;\n rem = state->wp - state->sp;\n if (rem < 2) return 1;\n\n unsigned char *src = state->data + state->sp;\n unsigned int accu = D0[src[0]] | D1[src[1]];\n int dp = 1;\n if (rem > 2 && src[2] != 61) {\n accu |= D2[src[2]];\n dp++;\n }\n if (rem == 4 && src[3] != 61) {\n accu |= D3[src[3]];\n dp++;\n }\n if (accu >> 24) return 1;\n *((unsigned int *) (state->data + state->dp)) = accu;\n state->dp += dp;\n return state->dp != state->b_size;\n }\n `\n}","hash":""} \ No newline at end of file diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm deleted file mode 100644 index c1ac6e8a..00000000 Binary files a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wasm and /dev/null differ diff --git a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat b/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat deleted file mode 100644 index d7832f45..00000000 --- a/addons/xterm-addon-image/inwasm-builds/out/base64.wasm.js/decode/final.wat +++ /dev/null @@ -1,210 +0,0 @@ -(module - (type (;0;) (func (result i32))) - (import "env" "memory" (memory (;0;) 1)) - (func (;0;) (type 0) (result i32) - (local i32 i32 i32 i32 i32) - i32.const 5128 - i32.load - i32.const 5152 - i32.add - local.set 1 - i32.const 5124 - i32.load - local.tee 0 - i32.const 5120 - i32.load - i32.const 1 - i32.sub - i32.const -4 - i32.and - local.tee 2 - i32.lt_s - if ;; label = @1 - local.get 2 - i32.const 5152 - i32.add - local.set 3 - local.get 0 - i32.const 5152 - i32.add - local.set 0 - loop ;; label = @2 - local.get 0 - i32.load8_u offset=3 - i32.const 2 - i32.shl - i32.load offset=4096 - local.get 0 - i32.load8_u offset=2 - i32.const 2 - i32.shl - i32.load offset=3072 - local.get 0 - i32.load8_u offset=1 - i32.const 2 - i32.shl - i32.load offset=2048 - local.get 0 - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=1024 - i32.or - i32.or - i32.or - local.tee 4 - i32.const 16777215 - i32.gt_u - if ;; label = @3 - i32.const 1 - return - end - local.get 1 - local.get 4 - i32.store - local.get 1 - i32.const 3 - i32.add - local.set 1 - local.get 0 - i32.const 4 - i32.add - local.tee 0 - local.get 3 - i32.lt_u - br_if 0 (;@2;) - end - end - i32.const 5124 - local.get 2 - i32.store - i32.const 5128 - local.get 1 - i32.const 5152 - i32.sub - i32.store - i32.const 0) - (func (;1;) (type 0) (result i32) - (local i32 i32 i32 i32 i32 i32) - block ;; label = @1 - i32.const 5120 - i32.load - local.tee 1 - i32.const 5124 - i32.load - local.tee 0 - i32.sub - i32.const 5 - i32.ge_s - if ;; label = @2 - i32.const 1 - local.set 3 - call 0 - br_if 1 (;@1;) - i32.const 5120 - i32.load - local.set 1 - i32.const 5124 - i32.load - local.set 0 - end - i32.const 1 - local.set 3 - local.get 1 - local.get 0 - i32.sub - local.tee 4 - i32.const 2 - i32.lt_s - br_if 0 (;@1;) - local.get 0 - i32.const 5153 - i32.add - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=2048 - local.get 0 - i32.const 5152 - i32.add - i32.load8_u - i32.const 2 - i32.shl - i32.load offset=1024 - i32.or - local.set 1 - block ;; label = @2 - local.get 4 - i32.const 2 - i32.eq - if ;; label = @3 - i32.const 1 - local.set 2 - br 1 (;@2;) - end - i32.const 1 - local.set 2 - local.get 0 - i32.load8_u offset=5154 - local.tee 5 - i32.const 61 - i32.ne - if ;; label = @3 - i32.const 2 - local.set 2 - local.get 5 - i32.const 2 - i32.shl - i32.load offset=3072 - local.get 1 - i32.or - local.set 1 - end - local.get 4 - i32.const 4 - i32.ne - br_if 0 (;@2;) - local.get 0 - i32.load8_u offset=5155 - local.tee 0 - i32.const 61 - i32.eq - br_if 0 (;@2;) - local.get 2 - i32.const 1 - i32.add - local.set 2 - local.get 0 - i32.const 2 - i32.shl - i32.load offset=4096 - local.get 1 - i32.or - local.set 1 - end - local.get 1 - i32.const 16777215 - i32.gt_u - br_if 0 (;@1;) - i32.const 5128 - i32.load - i32.const 5152 - i32.add - local.get 1 - i32.store - i32.const 5128 - i32.const 5128 - i32.load - local.get 2 - i32.add - local.tee 0 - i32.store - local.get 0 - i32.const 5136 - i32.load - i32.ne - local.set 3 - end - local.get 3) - (export "dec" (func 0)) - (export "end" (func 1))) diff --git a/addons/xterm-addon-image/package.json b/addons/xterm-addon-image/package.json index 69b87b38..8de858b5 100644 --- a/addons/xterm-addon-image/package.json +++ b/addons/xterm-addon-image/package.json @@ -17,8 +17,7 @@ "xterm.js" ], "scripts": { - "inwasm": "inwasm out/*.wasm.js", - "prepackage": "../../node_modules/.bin/tsc -p . && inwasm -f out/*.wasm.js", + "prepackage": "../../node_modules/.bin/tsc -p .", "package": "../../node_modules/.bin/webpack", "prepublishOnly": "npm run package" }, @@ -26,7 +25,7 @@ "xterm": "^5.2.0" }, "devDependencies": { - "inwasm": "^0.0.13", - "sixel": "^0.16.0" + "sixel": "^0.16.0", + "xterm-wasm-parts": "^0.1.0" } } diff --git a/addons/xterm-addon-image/src/IIPHandler.ts b/addons/xterm-addon-image/src/IIPHandler.ts index a8a851e6..ae62100d 100644 --- a/addons/xterm-addon-image/src/IIPHandler.ts +++ b/addons/xterm-addon-image/src/IIPHandler.ts @@ -5,7 +5,7 @@ import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types'; import { ImageRenderer } from './ImageRenderer'; import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage'; -import { Base64Decoder } from './base64.wasm'; +import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm'; import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser'; import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics'; diff --git a/addons/xterm-addon-image/src/ImageRenderer.ts b/addons/xterm-addon-image/src/ImageRenderer.ts index d225315e..9b528efe 100644 --- a/addons/xterm-addon-image/src/ImageRenderer.ts +++ b/addons/xterm-addon-image/src/ImageRenderer.ts @@ -6,6 +6,7 @@ import { toRGBA8888 } from 'sixel/lib/Colors'; import { IDisposable } from 'xterm'; import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; const PLACEHOLDER_LENGTH = 4096; @@ -17,12 +18,12 @@ const PLACEHOLDER_HEIGHT = 24; * - add canvas layer to DOM (browser only for now) * - draw image tiles onRender */ -export class ImageRenderer implements IDisposable { +export class ImageRenderer extends Disposable implements IDisposable { public canvas: HTMLCanvasElement | undefined; private _ctx: CanvasRenderingContext2D | null | undefined; private _placeholder: HTMLCanvasElement | undefined; private _placeholderBitmap: ImageBitmap | undefined; - private _optionsRefresh: IDisposable | undefined; + private _optionsRefresh = this.register(new MutableDisposable()); private _oldOpen: ((parent: HTMLElement) => void) | undefined; private _renderService: IRenderService | undefined; private _oldSetRenderer: ((renderer: any) => void) | undefined; @@ -68,6 +69,7 @@ export class ImageRenderer implements IDisposable { constructor(private _terminal: ITerminalExt) { + super(); this._oldOpen = this._terminal._core.open; this._terminal._core.open = (parent: HTMLElement): void => { this._oldOpen?.call(this._terminal._core, parent); @@ -77,32 +79,29 @@ export class ImageRenderer implements IDisposable { this._open(); } // hack to spot fontSize changes - this._optionsRefresh = this._terminal._core.optionsService.onOptionChange(option => { + this._optionsRefresh.value = this._terminal._core.optionsService.onOptionChange(option => { if (option === 'fontSize') { this.rescaleCanvas(); this._renderService?.refreshRows(0, this._terminal.rows); } }); - } - - - public dispose(): void { - this._optionsRefresh?.dispose(); - this.removeLayerFromDom(); - if (this._terminal._core && this._oldOpen) { - this._terminal._core.open = this._oldOpen; - this._oldOpen = undefined; - } - if (this._renderService && this._oldSetRenderer) { - this._renderService.setRenderer = this._oldSetRenderer; - this._oldSetRenderer = undefined; - } - this._renderService = undefined; - this.canvas = undefined; - this._ctx = undefined; - this._placeholderBitmap?.close(); - this._placeholderBitmap = undefined; - this._placeholder = undefined; + this.register(toDisposable(() => { + this.removeLayerFromDom(); + if (this._terminal._core && this._oldOpen) { + this._terminal._core.open = this._oldOpen; + this._oldOpen = undefined; + } + if (this._renderService && this._oldSetRenderer) { + this._renderService.setRenderer = this._oldSetRenderer; + this._oldSetRenderer = undefined; + } + this._renderService = undefined; + this.canvas = undefined; + this._ctx = undefined; + this._placeholderBitmap?.close(); + this._placeholderBitmap = undefined; + this._placeholder = undefined; + })); } /** diff --git a/addons/xterm-addon-image/src/base64.benchmark.ts b/addons/xterm-addon-image/src/base64.benchmark.ts deleted file mode 100644 index 6870295d..00000000 --- a/addons/xterm-addon-image/src/base64.benchmark.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { ThroughputRuntimeCase, perfContext } from 'xterm-benchmark'; -import { Base64Decoder } from './base64.wasm'; - -// eslint-disable-next-line -declare const Buffer: any; - -function toBytes(s: string): Uint8Array { - const bytes = new Uint8Array(s.length); - for (let i = 0; i < s.length; ++i) { - bytes[i] = s.charCodeAt(i) & 0xFF; - } - return bytes; -} - -const d256 = 'ABCD'.repeat(64); -const d4096 = 'ABCD'.repeat(64 * 16); -const d65536 = 'ABCD'.repeat(64 * 16 * 16); -const d1M = 'ABCD'.repeat(64 * 16 * 16 * 16); -const b256 = toBytes(d256); -const b4096 = toBytes(d4096); -const b65536 = toBytes(d65536); -const b1M = toBytes(d1M); -const dec = new Base64Decoder(4000000); - - -const RUNS = 100; - -perfContext('Base64', () => { - perfContext('Node - Buffer', () => { - new ThroughputRuntimeCase('decode - 256', () => { - Buffer.from(d256, 'base64'); - return { payloadSize: d256.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 4096', () => { - Buffer.from(d4096, 'base64'); - return { payloadSize: d4096.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 65536', () => { - Buffer.from(d65536, 'base64'); - return { payloadSize: d65536.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 1048576', () => { - Buffer.from(d1M, 'base64'); - return { payloadSize: d1M.length }; - }, { repeat: RUNS }).showAverageThroughput(); - }); - - perfContext('Base64Decoder', () => { - new ThroughputRuntimeCase('decode - 256', () => { - dec.init(192); - dec.put(b256, 0, b256.length); - dec.end(); - return { payloadSize: b256.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 4096', () => { - dec.init(3072); - dec.put(b4096, 0, b4096.length); - dec.end(); - return { payloadSize: b4096.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 65536', () => { - dec.init(49152); - dec.put(b65536, 0, b65536.length); - dec.end(); - return { payloadSize: b65536.length }; - }, { repeat: RUNS }).showAverageThroughput(); - - new ThroughputRuntimeCase('decode - 1048576', () => { - dec.init(786432); - dec.put(b1M, 0, b1M.length); - dec.end(); - return { payloadSize: b1M.length }; - }, { repeat: RUNS }).showAverageThroughput(); - }); -}); diff --git a/addons/xterm-addon-image/src/base64.test.ts b/addons/xterm-addon-image/src/base64.test.ts deleted file mode 100644 index 2d4e6d76..00000000 --- a/addons/xterm-addon-image/src/base64.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { assert } from 'chai'; -import { Base64Decoder } from './base64.wasm'; - -// eslint-disable-next-line -declare const Buffer: any; - - -// some helpers -function toBs(bytes: Uint8Array): string { - let bs = ''; - for (let i = 0; i < bytes.length; ++i) bs += String.fromCharCode(bytes[i]); - return bs; -} -function fromBs(bs: string): Uint8Array { - const r = new Uint8Array(bs.length); - for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i); - return r; -} -function encNative(bytes: Uint8Array): string { - return typeof Buffer !== 'undefined' ? Buffer.from(bytes).toString('base64') : btoa(toBs(bytes)); -} -function rtrim(x: string, c: string): string { - let end = x.length - 1; - while (c.indexOf(x[end]) >= 0) end -= 1; - return x.slice(0, end + 1); -} -const MAP = new Uint8Array( - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - .split('') - .map(el => el.charCodeAt(0)) -); - - -describe('Base64Decoder', () => { - describe('decoding', () => { - it('single bytes', function() { - this.timeout(20000); - const dec = new Base64Decoder(0); - for (let i = 0; i < 256; ++i) { - dec.init(1); - const inp = new Uint8Array([i]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - for (let a = 0; a < 256; ++a) { - it(`1+2 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(2); - const inp = new Uint8Array([a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - for (let a = 0; a < 256; ++a) { - it(`2+3 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(3); - const inp = new Uint8Array([0, a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - for (let a = 0; a < 256; ++a) { - it(`3+4 bytes (${a})`, function() { - const dec = new Base64Decoder(0); - for (let b = 0; b < 256; ++b) { - dec.init(4); - const inp = new Uint8Array([0, 0, a, b]); - const data = fromBs(encNative(inp)); - assert.strictEqual(dec.put(data, 0, data.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, inp); - } - }); - } - it('padding', () => { - const dec = new Base64Decoder(0); - const d = fromBs('Hello, here comes the mouse'); - const encData = []; - const encDataTrimmed = []; - for (let i = 1; i < d.length; ++i) { - encData.push(encNative(d.slice(0, i))); - encDataTrimmed.push(rtrim(encNative(d.slice(0, i)), '=')); - } - for (let i = 0; i < encData.length; ++i) { - // with padding - dec.init(i + 1); - let enc = fromBs(encData[i]); - assert.strictEqual(dec.put(enc, 0, enc.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, d.slice(0, i + 1)); - // w'o padding - dec.init(i + 1); - enc = fromBs(encDataTrimmed[i]); - assert.strictEqual(dec.put(enc, 0, enc.length), 0); - assert.strictEqual(dec.end(), 0); - assert.deepEqual(dec.data8, d.slice(0, i + 1)); - } - }); - it('exit on false byte', function() { - this.timeout(20000); - const dec = new Base64Decoder(0); - for (let pos = 0; pos < 8; ++pos) { - const inp = new Uint8Array([65, 65, 65, 65, 65, 65, 65, 65]); - for (let i = 0; i < 256; ++i) { - dec.release(); - dec.init(6); - inp[pos] = i; - dec.put(inp, 0, 8); - assert.strictEqual(dec.end(), MAP.includes(i) ? 0 : 1); - } - } - }); - }); - describe('memory', () => { - it('always release (keepSize 0)', () => { - const dec = new Base64Decoder(0); - dec.init(16); - dec.put(fromBs('A'.repeat(16)), 0, 16); - dec.end(); - assert.strictEqual(dec.data8.length, 12); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNull((dec as any)._mem); - }); - it('keep 1 page (keepSize 65536)', () => { - const dec = new Base64Decoder(65536); - dec.init(384); - dec.put(fromBs('A'.repeat(512)), 0, 512); - dec.end(); - assert.strictEqual(dec.data8.length, 384); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNotNull((dec as any)._mem); - // grow to 2 pages + free afterwards - dec.init(65536); - dec.put(fromBs('A'.repeat(65536)), 0, 65536); - dec.end(); - assert.strictEqual(dec.data8.length, 49152); - dec.release(); - assert.strictEqual(dec.data8.length, 0); - assert.isNull((dec as any)._mem); - }); - }); -}); diff --git a/addons/xterm-addon-image/src/base64.wasm.ts b/addons/xterm-addon-image/src/base64.wasm.ts deleted file mode 100644 index 02965c2d..00000000 --- a/addons/xterm-addon-image/src/base64.wasm.ts +++ /dev/null @@ -1,372 +0,0 @@ -/** - * Copyright (c) 2023 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { InWasm, IWasmInstance, OutputMode, OutputType } from 'inwasm'; - - -// memory addresses in uint32 -const enum P32 { - D0 = 256, - D1 = 512, - D2 = 768, - D3 = 1024, - STATE = 1280, - STATE_WP = 1280, - STATE_SP = 1281, - STATE_DP = 1282, - STATE_ESIZE = 1283, - STATE_BSIZE = 1284, - STATE_DATA = 1288 // 16 aligned -} - -/** - * wasm base64 decoder. - */ -const wasmDecode = InWasm({ - name: 'decode', - type: OutputType.INSTANCE, - mode: OutputMode.SYNC, - srctype: 'Clang-C', - imports: { - env: { memory: new WebAssembly.Memory({ initial: 1 }) } - }, - exports: { - dec: () => 0, - end: () => 0 - }, - compile: { - switches: ['-Wl,-z,stack-size=0', '-Wl,--stack-first'] - }, - code: ` - typedef struct { - unsigned int wp; - unsigned int sp; - unsigned int dp; - unsigned int e_size; - unsigned int b_size; - unsigned int dummy[3]; - unsigned char data[0]; - } State; - - unsigned int *D0 = (unsigned int *) ${P32.D0*4}; - unsigned int *D1 = (unsigned int *) ${P32.D1*4}; - unsigned int *D2 = (unsigned int *) ${P32.D2*4}; - unsigned int *D3 = (unsigned int *) ${P32.D3*4}; - State *state = (State *) ${P32.STATE*4}; - - __attribute__((noinline)) int dec() { - unsigned int nsp = (state->wp - 1) & ~3; - unsigned char *src = state->data + state->sp; - unsigned char *end = state->data + nsp; - unsigned char *dst = state->data + state->dp; - unsigned int accu; - - while (src < end) { - if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1; - *((unsigned int *) dst) = accu; - dst += 3; - src += 4; - } - state->sp = nsp; - state->dp = dst - state->data; - return 0; - } - - int end() { - int rem = state->wp - state->sp; - if (rem > 4 && dec()) return 1; - rem = state->wp - state->sp; - if (rem < 2) return 1; - - unsigned char *src = state->data + state->sp; - unsigned int accu = D0[src[0]] | D1[src[1]]; - int dp = 1; - if (rem > 2 && src[2] != 61) { - accu |= D2[src[2]]; - dp++; - } - if (rem == 4 && src[3] != 61) { - accu |= D3[src[3]]; - dp++; - } - if (accu >> 24) return 1; - *((unsigned int *) (state->data + state->dp)) = accu; - state->dp += dp; - return state->dp != state->b_size; - } - ` -}); - -// SIMD version - commented out for now due to missing Safari support -// const wasmDecode = InWasm({ -// name: 'decode', -// type: OutputType.INSTANCE, -// mode: OutputMode.SYNC, -// srctype: 'Clang-C', -// imports: { -// env: { memory: new WebAssembly.Memory({ initial: 1 }) } -// }, -// exports: { -// dec: () => 0, -// end: () => 0 -// }, -// compile: { -// switches: ['-msimd128', '-Wl,-z,stack-size=0', '-Wl,--stack-first'] -// }, -// code: ` -// #include -// typedef struct { -// unsigned int wp; -// unsigned int sp; -// unsigned int dp; -// unsigned int e_size; -// unsigned int b_size; -// unsigned int dummy[3]; -// unsigned char data[0]; -// } State; -// -// unsigned int *D0 = (unsigned int *) ${P32.D0*4}; -// unsigned int *D1 = (unsigned int *) ${P32.D1*4}; -// unsigned int *D2 = (unsigned int *) ${P32.D2*4}; -// unsigned int *D3 = (unsigned int *) ${P32.D3*4}; -// State *state = (State *) ${P32.STATE*4}; -// -// #define packed_byte(x) wasm_i8x16_splat((char) x) -// #define packed_dword(x) wasm_i32x4_splat(x) -// #define masked(x, mask) wasm_v128_and(x, wasm_i32x4_splat(mask)) -// -// int dec4() { -// unsigned int nsp = (state->wp - 1) & ~3; -// unsigned char *src = state->data + state->sp; -// unsigned char *end = state->data + nsp; -// unsigned char *dst = state->data + state->dp; -// unsigned int accu; -// -// while (src < end) { -// if ((accu = D0[src[0]] | D1[src[1]] | D2[src[2]] | D3[src[3]]) >> 24) return 1; -// *((unsigned int *) dst) = accu; -// dst += 3; -// src += 4; -// } -// state->sp = nsp; -// state->dp = dst - state->data; -// return 0; -// } -// -// int dec() { -// unsigned int nsp = (state->wp - 1) & ~15; -// unsigned char *src = state->data + state->sp; -// unsigned char *end = state->data + nsp; -// unsigned char *dst = state->data + state->dp; -// unsigned int accu; -// -// v128_t err = wasm_i8x16_splat(0); -// -// while (src < end) { -// v128_t data = wasm_v128_load((v128_t *) src); -// -// // wasm-simd rewrite of http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-pshufb -// const v128_t higher_nibble = wasm_u32x4_shr(data, 4) & packed_byte(0x0f); -// const char linv = 1; -// const char hinv = 0; -// -// const v128_t lower_bound_LUT = wasm_i8x16_make( -// /* 0 */ linv, /* 1 */ linv, /* 2 */ 0x2b, /* 3 */ 0x30, -// /* 4 */ 0x41, /* 5 */ 0x50, /* 6 */ 0x61, /* 7 */ 0x70, -// /* 8 */ linv, /* 9 */ linv, /* a */ linv, /* b */ linv, -// /* c */ linv, /* d */ linv, /* e */ linv, /* f */ linv -// ); -// const v128_t upper_bound_LUT = wasm_i8x16_make( -// /* 0 */ hinv, /* 1 */ hinv, /* 2 */ 0x2b, /* 3 */ 0x39, -// /* 4 */ 0x4f, /* 5 */ 0x5a, /* 6 */ 0x6f, /* 7 */ 0x7a, -// /* 8 */ hinv, /* 9 */ hinv, /* a */ hinv, /* b */ hinv, -// /* c */ hinv, /* d */ hinv, /* e */ hinv, /* f */ hinv -// ); -// // the difference between the shift and lower bound -// const v128_t shift_LUT = wasm_i8x16_make( -// /* 0 */ 0x00, /* 1 */ 0x00, /* 2 */ 0x3e - 0x2b, /* 3 */ 0x34 - 0x30, -// /* 4 */ 0x00 - 0x41, /* 5 */ 0x0f - 0x50, /* 6 */ 0x1a - 0x61, /* 7 */ 0x29 - 0x70, -// /* 8 */ 0x00, /* 9 */ 0x00, /* a */ 0x00, /* b */ 0x00, -// /* c */ 0x00, /* d */ 0x00, /* e */ 0x00, /* f */ 0x00 -// ); -// -// const v128_t upper_bound = wasm_i8x16_swizzle(upper_bound_LUT, higher_nibble); -// const v128_t lower_bound = wasm_i8x16_swizzle(lower_bound_LUT, higher_nibble); -// -// const v128_t below = wasm_i8x16_lt(data, lower_bound); -// const v128_t above = wasm_i8x16_gt(data, upper_bound); -// const v128_t eq_2f = wasm_i8x16_eq(data, packed_byte(0x2f)); -// -// // in_range = not (below or above) or eq_2f -// // outside = not in_range = below or above and not eq_2f (from deMorgan law) -// const v128_t outside = wasm_v128_andnot(eq_2f, above | below); -// err = wasm_v128_or(err, outside); -// -// const v128_t shift = wasm_i8x16_swizzle(shift_LUT, higher_nibble); -// const v128_t t0 = wasm_i8x16_add(data, shift); -// v128_t v = wasm_i8x16_add(t0, wasm_v128_and(eq_2f, packed_byte(-3))); -// -// // pack bytes -// const v128_t ca = masked(v, 0x003f003f); -// const v128_t db = masked(v, 0x3f003f00); -// const v128_t t00 = wasm_v128_or(wasm_u32x4_shr(db, 8), wasm_i32x4_shl(ca, 6)); -// v128_t res = wasm_v128_or(wasm_u32x4_shr(t00, 16), wasm_i32x4_shl(t00, 12)); -// res = wasm_i8x16_swizzle(res, wasm_i8x16_const(2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, 16, 16, 16, 16)); -// -// wasm_v128_store((v128_t *) dst, res); -// dst += 12; -// src += 16; -// } -// -// if (wasm_i8x16_bitmask(err) != 0) return 1; -// -// state->sp = nsp; -// state->dp = dst - state->data; -// return 0; -// } -// -// int end() { -// int rem = state->wp - state->sp; -// if (rem > 4 && dec4()) return 1; -// rem = state->wp - state->sp; -// if (rem < 2) return 1; -// -// unsigned char *src = state->data + state->sp; -// unsigned int accu = D0[src[0]] | D1[src[1]]; -// int dp = 1; -// if (rem > 2 && src[2] != 61) { -// accu |= D2[src[2]]; -// dp++; -// } -// if (rem == 4 && src[3] != 61) { -// accu |= D3[src[3]]; -// dp++; -// } -// if (accu >> 24) return 1; -// *((unsigned int *) (state->data + state->dp)) = accu; -// state->dp += dp; -// return state->dp != state->b_size; -// } -// ` -// }); - -// FIXME: currently broken in inwasm -type ExtractDefinition = Type extends () => IWasmInstance ? X : never; -type DecodeDefinition = ExtractDefinition; - -// base64 map -const MAP = new Uint8Array( - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - .split('') - .map(el => el.charCodeAt(0)) -); - -// init decoder maps in LE order -const D = new Uint32Array(1024); -D.fill(0xFF000000); -for (let i = 0; i < MAP.length; ++i) D[MAP[i]] = i << 2; -for (let i = 0; i < MAP.length; ++i) D[256 + MAP[i]] = i >> 4 | ((i << 4) & 0xFF) << 8; -for (let i = 0; i < MAP.length; ++i) D[512 + MAP[i]] = (i >> 2) << 8 | ((i << 6) & 0xFF) << 16; -for (let i = 0; i < MAP.length; ++i) D[768 + MAP[i]] = i << 16; - -const EMPTY = new Uint8Array(0); - -/** - * base64 streamline inplace decoder. - * - * Features / assumptions: - * - optimized uint32 read/write (only LE support!) - * - lazy chunkwise decoding - * - errors out on any non base64 chars (no support for NL formatted base64) - * - decodes in wasm - * - inplace decoding to save memory - * - supports a keepSize for lazy memory release - */ -export class Base64Decoder { - private _d!: Uint8Array; - private _m32!: Uint32Array; - private _inst!: IWasmInstance; - private _mem!: WebAssembly.Memory; - - constructor(public keepSize: number) {} - - /** - * Currently decoded bytes (borrowed). - * Must be accessed before calling `release` or `init`. - */ - public get data8(): Uint8Array { - return this._inst ? this._d.subarray(0, this._m32[P32.STATE_DP]) : EMPTY; - } - - /** - * Release memory conditionally based on `keepSize`. - * If memory gets released, also the wasm instance will be freed and recreated on next `init`, - * otherwise the instance will be reused. - */ - public release(): void { - if (!this._inst) return; - if (this._mem.buffer.byteLength > this.keepSize) { - this._inst = this._m32 = this._d = this._mem = null!; - } else { - this._m32[P32.STATE_WP] = 0; - this._m32[P32.STATE_SP] = 0; - this._m32[P32.STATE_DP] = 0; - } - } - - /** - * Initializes the decoder for new base64 data. - * Must be called before doing any decoding attempts. - * `size` is the amount of decoded bytes to be expected. - * The method will either spawn a new wasm instance or grow - * the needed memory of an existing instance. - */ - public init(size: number): void { - let m = this._m32; - const bytes = (Math.ceil(size / 3) + P32.STATE_DATA) * 4; - if (!this._inst) { - this._mem = new WebAssembly.Memory({ initial: Math.ceil(bytes / 65536) }); - this._inst = wasmDecode({ env: { memory: this._mem } }); - m = new Uint32Array(this._mem.buffer, 0); - m.set(D, P32.D0); - this._d = new Uint8Array(this._mem.buffer, P32.STATE_DATA * 4); - } else if (this._mem.buffer.byteLength < bytes) { - this._mem.grow(Math.ceil((bytes - this._mem.buffer.byteLength) / 65536)); - m = new Uint32Array(this._mem.buffer, 0); - this._d = new Uint8Array(this._mem.buffer, P32.STATE_DATA * 4); - } - m[P32.STATE_BSIZE] = size; - m[P32.STATE_ESIZE] = Math.ceil(size / 3) * 4; - m[P32.STATE_WP] = 0; - m[P32.STATE_SP] = 0; - m[P32.STATE_DP] = 0; - this._m32 = m; - } - - /** - * Put bytes in `data` from `start` to `end` (exclusive) into the decoder. - * Also decodes base64 data inplace once the payload exceeds 2^17 bytes. - * Returns 1 on error, else 0. - */ - public put(data: Uint8Array | Uint16Array | Uint32Array, start: number, end: number): number { - if (!this._inst) return 1; - const m = this._m32; - if (end - start + m[P32.STATE_WP] > m[P32.STATE_ESIZE]) return 1; - this._d.set(data.subarray(start, end), m[P32.STATE_WP]); - m[P32.STATE_WP] += end - start; - // max chunk in input handler is 2^17, try to run in "tandem mode" - // also assures that we dont run into illegal offsets in the wasm part - return m[P32.STATE_WP] - m[P32.STATE_SP] >= 131072 ? this._inst.exports.dec() : 0; - } - - /** - * End the current decoding. - * Decodes leftover payload and finally checks for the correct amount of - * decoded bytes by comparing to the value given to `init`. - * Returns 1 on error, else 0. - */ - public end(): number { - return this._inst ? this._inst.exports.end() : 1; - } -} diff --git a/addons/xterm-addon-search/src/SearchAddon.ts b/addons/xterm-addon-search/src/SearchAddon.ts index cbde1a1b..10fc1ecc 100644 --- a/addons/xterm-addon-search/src/SearchAddon.ts +++ b/addons/xterm-addon-search/src/SearchAddon.ts @@ -5,7 +5,7 @@ import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm'; import { EventEmitter } from 'common/EventEmitter'; -import { Disposable, toDisposable, disposeArray } from 'common/Lifecycle'; +import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; export interface ISearchOptions { regex?: boolean; @@ -66,7 +66,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _cachedSearchTerm: string | undefined; private _highlightedLines: Set = new Set(); private _highlightDecorations: IHighlight[] = []; - private _selectedDecoration: IHighlight | undefined; + private _selectedDecoration: MutableDisposable = this.register(new MutableDisposable()); private _highlightLimit: number; private _lastSearchOptions: ISearchOptions | undefined; private _highlightTimeout: number | undefined; @@ -110,7 +110,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } public clearDecorations(retainCachedSearchTerm?: boolean): void { - this.clearActiveDecoration(); + this._selectedDecoration.clear(); disposeArray(this._highlightDecorations); this._highlightDecorations = []; this._highlightedLines.clear(); @@ -119,11 +119,6 @@ export class SearchAddon extends Disposable implements ITerminalAddon { } } - public clearActiveDecoration(): void { - this._selectedDecoration?.dispose(); - this._selectedDecoration = undefined; - } - /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. @@ -320,8 +315,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon { private _fireResults(searchOptions?: ISearchOptions): void { if (searchOptions?.decorations) { let resultIndex = -1; - if (this._selectedDecoration) { - const selectedMatch = this._selectedDecoration.match; + if (this._selectedDecoration.value) { + const selectedMatch = this._selectedDecoration.value.match; for (let i = 0; i < this._highlightDecorations.length; i++) { const match = this._highlightDecorations[i].match; if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) { @@ -642,7 +637,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { */ private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean { const terminal = this._terminal!; - this.clearActiveDecoration(); + this._selectedDecoration.clear(); if (!result) { terminal.clearSelection(); return false; @@ -666,7 +661,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon { disposables.push(marker); disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true))); disposables.push(decoration.onDispose(() => disposeArray(disposables))); - this._selectedDecoration = { decoration, match: result, dispose() { decoration.dispose(); } }; + this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } }; } } } diff --git a/addons/xterm-addon-serialize/src/SerializeAddon.ts b/addons/xterm-addon-serialize/src/SerializeAddon.ts index ace31315..7a2db089 100644 --- a/addons/xterm-addon-serialize/src/SerializeAddon.ts +++ b/addons/xterm-addon-serialize/src/SerializeAddon.ts @@ -414,8 +414,6 @@ class StringSerializeHandler extends BaseSerializeHandler { export class SerializeAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() { } - public activate(terminal: Terminal): void { this._terminal = terminal; } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 20db5faf..9a8f8ee3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -20,7 +20,7 @@ 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 { IDisposable, Terminal } from 'xterm'; +import { Terminal } from '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'; @@ -31,7 +31,7 @@ import { IRenderLayer } from './renderLayer/Types'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; private _cursorBlinkStateManager: MutableDisposable = new MutableDisposable(); - private _charAtlasDisposable: IDisposable | undefined; + private _charAtlasDisposable = this.register(new MutableDisposable()); private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; @@ -41,8 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; - private _rectangleRenderer?: RectangleRenderer; - private _glyphRenderer?: GlyphRenderer; + private _rectangleRenderer: MutableDisposable = this.register(new MutableDisposable()); + private _glyphRenderer: MutableDisposable = this.register(new MutableDisposable()); public readonly dimensions: IRenderDimensions; @@ -128,7 +128,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.appendChild(this._canvas); - [this._rectangleRenderer, this._glyphRenderer] = this._initializeWebGLState(); + [this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState(); this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!); @@ -182,10 +182,10 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`; this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`; - this._rectangleRenderer?.setDimensions(this.dimensions); - this._rectangleRenderer?.handleResize(); - this._glyphRenderer?.setDimensions(this.dimensions); - this._glyphRenderer?.handleResize(); + this._rectangleRenderer.value?.setDimensions(this.dimensions); + this._rectangleRenderer.value?.handleResize(); + this._glyphRenderer.value?.setDimensions(this.dimensions); + this._glyphRenderer.value?.handleResize(); this._refreshCharAtlas(); @@ -241,17 +241,13 @@ export class WebglRenderer extends Disposable implements IRenderer { * Initializes members dependent on WebGL context state. */ private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { - // Dispose any previous rectangle and glyph renderers before creating new ones. - this._rectangleRenderer?.dispose(); - this._glyphRenderer?.dispose(); - - this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService)); - this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions)); + this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); + this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions); // Update dimensions and acquire char atlas this.handleCharSizeChanged(); - return [this._rectangleRenderer, this._glyphRenderer]; + return [this._rectangleRenderer.value, this._glyphRenderer.value]; } /** @@ -275,16 +271,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._coreBrowserService.dpr ); if (this._charAtlas !== atlas) { - this._charAtlasDisposable?.dispose(); this._onChangeTextureAtlas.fire(atlas.pages[0].canvas); - this._charAtlasDisposable = getDisposeArrayDisposable([ + this._charAtlasDisposable.value = getDisposeArrayDisposable([ forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas), forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas) ]); } this._charAtlas = atlas; this._charAtlas.warmUp(); - this._glyphRenderer?.setAtlas(this._charAtlas); + this._glyphRenderer.value?.setAtlas(this._charAtlas); } /** @@ -295,7 +290,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _clearModel(clearGlyphRenderer: boolean): void { this._model.clear(); if (clearGlyphRenderer) { - this._glyphRenderer?.clear(); + this._glyphRenderer.value?.clear(); } } @@ -340,14 +335,14 @@ export class WebglRenderer extends Disposable implements IRenderer { l.handleGridChanged(this._terminal, start, end); } - if (!this._glyphRenderer || !this._rectangleRenderer) { + if (!this._glyphRenderer.value || !this._rectangleRenderer.value) { return; } // Tell renderer the frame is beginning // upon a model clear also refresh the full viewport model // (also triggered by an atlas page merge, part of #4480) - if (this._glyphRenderer.beginFrame()) { + if (this._glyphRenderer.value.beginFrame()) { this._clearModel(true); this._updateModel(0, this._terminal.rows - 1); } else { @@ -356,10 +351,10 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Render - this._rectangleRenderer?.renderBackgrounds(); - this._glyphRenderer?.render(this._model); + this._rectangleRenderer.value.renderBackgrounds(); + this._glyphRenderer.value.render(this._model); if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) { - this._rectangleRenderer?.renderCursor(); + this._rectangleRenderer.value.renderCursor(); } } @@ -502,7 +497,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); + this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (isJoined) { // Restore work cell @@ -511,7 +506,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Null out non-first cells for (x++; x < lastCharX; x++) { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; - this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); + this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; @@ -521,9 +516,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } } if (modelUpdated) { - this._rectangleRenderer!.updateBackgrounds(this._model); + this._rectangleRenderer.value!.updateBackgrounds(this._model); } - this._rectangleRenderer!.updateCursor(this._model); + this._rectangleRenderer.value!.updateCursor(this._model); } /** diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index f5449c4d..3aaac435 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -49,7 +49,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer })); this.register(toDisposable(() => { this._canvas.remove(); - this._charAtlas?.dispose(); })); } diff --git a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts index 5d9cc0f0..24485cb2 100644 --- a/addons/xterm-addon-webgl/test/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/test/WebglRenderer.api.ts @@ -1106,7 +1106,7 @@ describe('WebGL Renderer Integration Tests', async () => { async function getCellColor(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( @@ -1120,7 +1120,7 @@ async function getCellColor(col: number, row: number): Promise { async function getCellPixels(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderService._renderer._gl; + window.gl = window.term._core._renderService._renderer.value._gl; window.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4); window.d = window.term._core._renderService.dimensions; window.gl.readPixels( diff --git a/demo/client.ts b/demo/client.ts index 7b295c37..09084eb5 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -62,7 +62,7 @@ let socket; let pid; let autoResize: boolean = true; -type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicode-graphemes' | 'web-links' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicode-graphemes' | 'webLinks' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -74,7 +74,7 @@ interface IDemoAddon { T extends 'image' ? typeof ImageAddon : T extends 'search' ? typeof SearchAddon : T extends 'serialize' ? typeof SerializeAddon : - T extends 'web-links' ? typeof WebLinksAddon : + T extends 'webLinks' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : T extends 'unicode-graphemes' ? typeof UnicodeGraphemesAddon : T extends 'ligatures' ? typeof LigaturesAddon : @@ -87,7 +87,7 @@ interface IDemoAddon { T extends 'image' ? ImageAddon : T extends 'search' ? SearchAddon : T extends 'serialize' ? SerializeAddon : - T extends 'web-links' ? WebLinksAddon : + T extends 'webLinks' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : T extends 'unicode11' ? typeof Unicode11Addon : T extends 'unicode-graphemes' ? typeof UnicodeGraphemesAddon : @@ -103,7 +103,7 @@ const addons: { [T in AddonType]: IDemoAddon } = { image: { name: 'image', ctor: ImageAddon, canChange: true }, search: { name: 'search', ctor: SearchAddon, canChange: true }, serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true }, - 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, + webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, 'unicode-graphemes': { name: 'unicode-graphemes', ctor: UnicodeGraphemesAddon, canChange: true }, @@ -177,7 +177,7 @@ const disposeRecreateButtonHandler: () => void = () => { addons.unicode11.instance = undefined; addons['unicode-graphemes'].instance = undefined; addons.ligatures.instance = undefined; - addons['web-links'].instance = undefined; + addons.webLinks.instance = undefined; addons.webgl.instance = undefined; document.getElementById('dispose').innerHTML = 'Recreate Terminal'; } else { @@ -281,13 +281,13 @@ function createTerminal(): void { } catch (e) { console.warn(e); } - addons['web-links'].instance = new WebLinksAddon(); + addons.webLinks.instance = new WebLinksAddon(); typedTerm.loadAddon(addons.fit.instance); typedTerm.loadAddon(addons.image.instance); typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.serialize.instance); typedTerm.loadAddon(addons['unicode-graphemes'].instance); - typedTerm.loadAddon(addons['web-links'].instance); + typedTerm.loadAddon(addons.webLinks.instance); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { @@ -427,10 +427,13 @@ function initOptions(term: TerminalType): void { 'cancelEvents', 'convertEol', 'termName', - 'cols', 'rows', // subsumed by "size" (cols_rows) option + 'cols', 'rows', // subsumed by "size" (colsRows) option // Complex option + 'linkHandler', + 'logger', 'theme', - 'windowOptions' + 'windowOptions', + 'windowsPty' ]; const stringOptions = { cursorStyle: ['block', 'underline', 'bar'], @@ -442,7 +445,7 @@ function initOptions(term: TerminalType): void { logLevel: ['trace', 'debug', 'info', 'warn', 'error', 'off'], theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null, - cols_rows: null + colsRows: null }; const options = Object.getOwnPropertyNames(term.options); const booleanOptions = []; @@ -475,7 +478,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; Object.keys(stringOptions).forEach(o => { - if (o === 'cols_rows') { + if (o === 'colsRows') { html += `
`; } else if (stringOptions[o]) { const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; @@ -520,8 +523,8 @@ function initOptions(term: TerminalType): void { addDomListener(input, 'change', () => { console.log('change', o, input.value); let value: any = input.value; - if (o === 'cols_rows') { - let m = input.value.match(/^([0-9]+)x([0-9]+)$/); + if (o === 'colsRows') { + const m = input.value.match(/^([0-9]+)x([0-9]+)$/); if (m) { autoResize = false; term.resize(parseInt(m[1]), parseInt(m[2])); @@ -1307,29 +1310,29 @@ function initImageAddonExposed(): void { const ctorOptionsElement = document.querySelector('#image-options'); ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2); - const sixel_demo = (url: string) => () => fetch(url) + const sixelDemo = (url: string) => () => fetch(url) .then(resp => resp.arrayBuffer()) .then(buffer => { term.write('\r\n'); term.write(new Uint8Array(buffer)); }); - const iip_demo = (url: string) => () => fetch(url) - .then(resp => resp.arrayBuffer()) - .then(buffer => { - const data = new Uint8Array(buffer); - let sdata = ''; - for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); - term.write('\r\n'); - term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); - }); + const iipDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + const data = new Uint8Array(buffer); + let sdata = ''; + for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); + term.write('\r\n'); + term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); + }); document.getElementById('image-demo1').addEventListener('click', - sixel_demo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); + sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); document.getElementById('image-demo2').addEventListener('click', - sixel_demo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); + sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); document.getElementById('image-demo3').addEventListener('click', - iip_demo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); + iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); // demo for image retrieval API term.element.addEventListener('click', (ev: MouseEvent) => { diff --git a/demo/server.js b/demo/server.js index f477ae79..90ed2842 100644 --- a/demo/server.js +++ b/demo/server.js @@ -4,21 +4,23 @@ * demo to the public as is would introduce security risks for the host. **/ -var express = require('express'); -var expressWs = require('express-ws'); -var os = require('os'); -var pty = require('node-pty'); +// @ts-check -// Whether to use binary transport. +const express = require('express'); +const expressWs = require('express-ws'); +const os = require('os'); +const pty = require('node-pty'); + +/** Whether to use binary transport. */ const USE_BINARY = os.platform() !== "win32"; function startServer() { - var app = express(); - expressWs(app); + const app = express(); + const appWs = expressWs(app).app; - var terminals = {}, - unsentOutput = {}, - temporaryDisposable = {}; + const terminals = {}; + const unsentOutput = {}; + const temporaryDisposable = {}; app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css')); app.get('/logo.png', (req, res) => { @@ -41,18 +43,30 @@ function startServer() { app.use('/src', express.static(__dirname + '/src')); app.post('/terminals', (req, res) => { - const env = Object.assign({}, process.env); + /** @type {{ [key: string]: string }} */ + const env = {}; + for (const k of Object.keys(process.env)) { + const v = process.env[k]; + if (v) { + env[k] = v; + } + } + // const env = Object.assign({}, process.env); env['COLORTERM'] = 'truecolor'; - var cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = pty.spawn(process.platform === 'win32' ? 'pwsh.exe' : 'bash', [], { - name: 'xterm-256color', - cols: cols || 80, - rows: rows || 24, - cwd: process.platform === 'win32' ? undefined : env.PWD, - env: env, - encoding: USE_BINARY ? null : 'utf8' - }); + if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') { + console.error({ req }); + throw new Error('Unexpected query args'); + } + const cols = parseInt(req.query.cols); + const rows = parseInt(req.query.rows); + const term = pty.spawn(process.platform === 'win32' ? 'pwsh.exe' : 'bash', [], { + name: 'xterm-256color', + cols: cols ?? 80, + rows: rows ?? 24, + cwd: process.platform === 'win32' ? undefined : env.PWD, + env, + encoding: USE_BINARY ? null : 'utf8' + }); console.log('Created terminal with PID: ' + term.pid); terminals[term.pid] = term; @@ -65,18 +79,22 @@ function startServer() { }); app.post('/terminals/:pid/size', (req, res) => { - var pid = parseInt(req.params.pid), - cols = parseInt(req.query.cols), - rows = parseInt(req.query.rows), - term = terminals[pid]; + if (typeof req.query.cols !== 'string' || typeof req.query.rows !== 'string') { + console.error({ req }); + throw new Error('Unexpected query args'); + } + const pid = parseInt(req.params.pid); + const cols = parseInt(req.query.cols); + const rows = parseInt(req.query.rows); + const term = terminals[pid]; term.resize(cols, rows); console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.'); res.end(); }); - app.ws('/terminals/:pid', function (ws, req) { - var term = terminals[parseInt(req.params.pid)]; + appWs.ws('/terminals/:pid', function (ws, req) { + const term = terminals[parseInt(req.params.pid)]; console.log('Connected to terminal ' + term.pid); temporaryDisposable[term.pid].dispose(); delete temporaryDisposable[term.pid]; @@ -160,11 +178,11 @@ function startServer() { }); }); - var port = process.env.PORT || 3000, - host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; + const port = parseInt(process.env.PORT ?? '3000'); + const host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0'; console.log('App listening to http://127.0.0.1:' + port); - app.listen(port, host); + app.listen(port, host, 0); } module.exports = startServer; diff --git a/demo/start.js b/demo/start.js index b40b9bc3..7349f36d 100644 --- a/demo/start.js +++ b/demo/start.js @@ -1,10 +1,10 @@ /** * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT - * - * This file is the entry point for browserify. */ +// @ts-check + const path = require('path'); const webpack = require('webpack'); const startServer = require('./server.js'); @@ -20,6 +20,8 @@ startServer(); * For production builds see `webpack.config.js` in the root directory. If that is built the demo * can use that by switching out which `Terminal` is imported in `client.ts`, this is useful for * validating that the packaged version works correctly. + * + * @type {import('webpack').Configuration} */ const clientConfig = { entry: path.resolve(__dirname, 'client.ts'), @@ -69,12 +71,13 @@ const clientConfig = { const compiler = webpack(clientConfig); compiler.watch({ - // Example watchOptions aggregateTimeout: 300, poll: undefined }, (err, stats) => { - // Print watch/build result here... - console.log(stats.toString({ + if (err) { + console.error(err); + } + console.log(stats?.toString({ colors: true })); }); diff --git a/package.json b/package.json index be507b6e..70214933 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,8 @@ "install-addons": "node ./bin/install-addons.js", "presetup": "npm run install-addons", "setup": "npm run build", - "postsetup": "npm run inwasm", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", - "inwasm": "cd addons/xterm-addon-image && npm run inwasm -- -S", "benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json", "benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js", "benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js", @@ -58,6 +56,8 @@ "@types/chai": "^4.2.22", "@types/debug": "^4.1.7", "@types/deep-equal": "^1.0.1", + "@types/express": "4", + "@types/express-ws": "^3.0.1", "@types/glob": "^7.2.0", "@types/jsdom": "^16.2.13", "@types/mocha": "^9.0.0", diff --git a/src/browser/Linkifier2.test.ts b/src/browser/Linkifier2.test.ts index c1a0cf6c..0af74c28 100644 --- a/src/browser/Linkifier2.test.ts +++ b/src/browser/Linkifier2.test.ts @@ -10,10 +10,6 @@ import { MockBufferService } from 'common/TestUtils.test'; import { ILink } from 'browser/Types'; class TestLinkifier2 extends Linkifier2 { - constructor(bufferService: IBufferService) { - super(bufferService); - } - public set currentLink(link: any) { this._currentLink = link; } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 24dace2f..a092e1bc 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -44,7 +44,7 @@ import { ThemeService } from 'browser/services/ThemeService'; import { color, rgba } from 'common/Color'; import { CoreTerminal } from 'common/CoreTerminal'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; -import { toDisposable } from 'common/Lifecycle'; +import { MutableDisposable, toDisposable } from 'common/Lifecycle'; import * as Browser from 'common/Platform'; import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -118,7 +118,7 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier2: ILinkifier2; public viewport: IViewport | undefined; private _compositionHelper: ICompositionHelper | undefined; - private _accessibilityManager: AccessibilityManager | undefined; + private _accessibilityManager: MutableDisposable = this.register(new MutableDisposable()); private readonly _onCursorMove = this.register(new EventEmitter()); public readonly onCursorMove = this._onCursorMove.event; @@ -252,12 +252,11 @@ export class Terminal extends CoreTerminal implements ITerminal { private _handleScreenReaderModeOptionChange(value: boolean): void { if (value) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + if (!this._accessibilityManager.value && this._renderService) { + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = undefined; + this._accessibilityManager.clear(); } } @@ -535,7 +534,7 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.options.screenReaderMode) { // Note that this must be done *after* the renderer is created in order to // ensure the correct order of the dprchange event - this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this); + this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this); } this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e))); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 4db5f7aa..2c75d7b8 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -236,7 +236,7 @@ export class Terminal extends Disposable implements ITerminalApi { this._core.clearTextureAtlas(); } public loadAddon(addon: ITerminalAddon): void { - return this._addonManager.loadAddon(this, addon); + this._addonManager.loadAddon(this, addon); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index c7b85a7e..dd059574 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -151,50 +151,48 @@ export class TextureAtlas implements ITextureAtlas { // microtask to ensure it does not interrupt textures that will be rendered in the current // animation frame which would result in blank rendered areas. This is actually not that // expensive relative to drawing the glyphs, so there is no need to wait for an idle callback. - if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages / 2)) { - queueMicrotask(() => { - // Find the set of the largest 4 images, below the maximum size, with the highest - // percentages used - const pagesBySize = this._pages.filter(e => { - return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE); - }).sort((a, b) => { - if (b.canvas.width !== a.canvas.width) { - return b.canvas.width - a.canvas.width; - } - return b.percentageUsed - a.percentageUsed; - }); - let sameSizeI = -1; - let size = 0; - for (let i = 0; i < pagesBySize.length; i++) { - if (pagesBySize[i].canvas.width !== size) { - sameSizeI = i; - size = pagesBySize[i].canvas.width; - } else if (i - sameSizeI === 3) { - break; - } + if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) { + // Find the set of the largest 4 images, below the maximum size, with the highest + // percentages used + const pagesBySize = this._pages.filter(e => { + return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE); + }).sort((a, b) => { + if (b.canvas.width !== a.canvas.width) { + return b.canvas.width - a.canvas.width; } - - // Gather details of the merge - const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); - const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1); - const mergedPageIndex = sortedMergingPagesIndexes[0]; - - // Merge into the new page - const mergedPage = this._mergePages(mergingPages, mergedPageIndex); - mergedPage.version++; - - // Replace the first _merging_ page with the _merged_ page - this._pages[mergedPageIndex] = mergedPage; - - // Delete the other 3 pages, shifting glyph texture pages as needed - for (let i = sortedMergingPagesIndexes.length - 1; i >= 1; i--) { - this._deletePage(sortedMergingPagesIndexes[i]); - } - - // Request the model to be cleared to refresh all texture pages. - this._requestClearModel = true; - this._onAddTextureAtlasCanvas.fire(mergedPage.canvas); + return b.percentageUsed - a.percentageUsed; }); + let sameSizeI = -1; + let size = 0; + for (let i = 0; i < pagesBySize.length; i++) { + if (pagesBySize[i].canvas.width !== size) { + sameSizeI = i; + size = pagesBySize[i].canvas.width; + } else if (i - sameSizeI === 3) { + break; + } + } + + // Gather details of the merge + const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4); + const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1); + const mergedPageIndex = this.pages.length - mergingPages.length; + + // Merge into the new page + const mergedPage = this._mergePages(mergingPages, mergedPageIndex); + mergedPage.version++; + + // Delete the pages, shifting glyph texture pages as needed + for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) { + this._deletePage(sortedMergingPagesIndexes[i]); + } + + // Add the new merged page to the end + this.pages.push(mergedPage); + + // Request the model to be cleared to refresh all texture pages. + this._requestClearModel = true; + this._onAddTextureAtlasCanvas.fire(mergedPage.canvas); } // All new atlas pages are created small as they are highly dynamic @@ -756,13 +754,13 @@ export class TextureAtlas implements ITextureAtlas { } } - // Create a new one if too much vertical space would be wasted or there is not enough room + // Create a new page if too much vertical space would be wasted or there is not enough room // left in the page. The previous active row will become fixed in the process as it now has a // fixed height if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) { // Create the new fixed height row, creating a new page if there isn't enough room on the // current page - let wasNewPageCreated = false; + let wasPageAndRowFound = false; if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) { // Find the first page with room to create the new row on let candidatePage: AtlasPage | undefined; @@ -775,15 +773,30 @@ export class TextureAtlas implements ITextureAtlas { if (candidatePage) { activePage = candidatePage; } else { - // Create a new page if there is no room - const newPage = this._createNewPage(); - activePage = newPage; - activeRow = newPage.currentRow; - activeRow.height = rasterizedGlyph.size.y; - wasNewPageCreated = true; + // Before creating a new atlas page that would trigger a page merge, check if the + // current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will + // improve texture utilization by using the available space before the page is merged + // and becomes static. + if ( + TextureAtlas.maxAtlasPages && + this._pages.length >= TextureAtlas.maxAtlasPages && + activeRow.y + rasterizedGlyph.size.y <= activePage.canvas.height && + activeRow.height >= rasterizedGlyph.size.y && + activeRow.x + rasterizedGlyph.size.x <= activePage.canvas.width + ) { + // activePage and activeRow is already valid + wasPageAndRowFound = true; + } else { + // Create a new page if there is no room + const newPage = this._createNewPage(); + activePage = newPage; + activeRow = newPage.currentRow; + activeRow.height = rasterizedGlyph.size.y; + wasPageAndRowFound = true; + } } } - if (!wasNewPageCreated) { + if (!wasPageAndRowFound) { // Fix the current row as the new row is being added below if (activePage.currentRow.height > 0) { activePage.fixedRows.push(activePage.currentRow); diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 41f0c9b5..0f18a233 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ 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 } from 'common/Lifecycle'; +import { Disposable, MutableDisposable } from 'common/Lifecycle'; import { DebouncedIdleTask } from 'common/TaskQueue'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -23,7 +23,7 @@ interface ISelectionState { export class RenderService extends Disposable implements IRenderService { public serviceBrand: undefined; - private _renderer: IRenderer | undefined; + private _renderer: MutableDisposable = this.register(new MutableDisposable()); private _renderDebouncer: IRenderDebouncerWithCallback; private _screenDprMonitor: ScreenDprMonitor; private _pausedResizeTask = new DebouncedIdleTask(); @@ -49,7 +49,7 @@ export class RenderService extends Disposable implements IRenderService { private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>()); public readonly onRefreshRequest = this._onRefreshRequest.event; - public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; } + public get dimensions(): IRenderDimensions { return this._renderer.value!.dimensions; } constructor( private _rowCount: number, @@ -63,8 +63,6 @@ export class RenderService extends Disposable implements IRenderService { ) { super(); - this.register({ dispose: () => this._renderer?.dispose() }); - this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end)); this.register(this._renderDebouncer); @@ -73,7 +71,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._screenDprMonitor); this.register(bufferService.onResize(() => this._fullRefresh())); - this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear())); + this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear())); this.register(optionsService.onOptionChange(() => this._handleOptionsChanged())); this.register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged())); @@ -148,7 +146,7 @@ export class RenderService extends Disposable implements IRenderService { } private _renderRows(start: number, end: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } @@ -159,11 +157,11 @@ export class RenderService extends Disposable implements IRenderService { end = Math.min(end, this._rowCount - 1); // Render - this._renderer.renderRows(start, end); + this._renderer.value.renderRows(start, end); // Update selection if needed if (this._needsSelectionRefresh) { - this._renderer.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); + this._renderer.value.handleSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode); this._needsSelectionRefresh = false; } @@ -181,7 +179,7 @@ export class RenderService extends Disposable implements IRenderService { } private _handleOptionsChanged(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } this.refreshRows(0, this._rowCount - 1); @@ -189,25 +187,23 @@ export class RenderService extends Disposable implements IRenderService { } private _fireOnCanvasResize(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } // Don't fire the event if the dimensions haven't changed - if (this._renderer.dimensions.css.canvas.width === this._canvasWidth && this._renderer.dimensions.css.canvas.height === this._canvasHeight) { + if (this._renderer.value.dimensions.css.canvas.width === this._canvasWidth && this._renderer.value.dimensions.css.canvas.height === this._canvasHeight) { return; } - this._onDimensionsChange.fire(this._renderer.dimensions); + this._onDimensionsChange.fire(this._renderer.value.dimensions); } public hasRenderer(): boolean { - return !!this._renderer; + return !!this._renderer.value; } public setRenderer(renderer: IRenderer): void { - // TODO: RenderService should be the only one to dispose the renderer - this._renderer?.dispose(); - this._renderer = renderer; - this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); + this._renderer.value = renderer; + this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true)); // Force a refresh this._needsSelectionRefresh = true; @@ -227,10 +223,10 @@ export class RenderService extends Disposable implements IRenderService { } public clearTextureAtlas(): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.clearTextureAtlas?.(); + this._renderer.value.clearTextureAtlas?.(); this._fullRefresh(); } @@ -239,50 +235,50 @@ export class RenderService extends Disposable implements IRenderService { // when devicePixelRatio changes this._charSizeService.measure(); - if (!this._renderer) { + if (!this._renderer.value) { return; } - this._renderer.handleDevicePixelRatioChange(); + this._renderer.value.handleDevicePixelRatioChange(); this.refreshRows(0, this._rowCount - 1); } public handleResize(cols: number, rows: number): void { - if (!this._renderer) { + if (!this._renderer.value) { return; } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer!.handleResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows)); } else { - this._renderer.handleResize(cols, rows); + this._renderer.value.handleResize(cols, rows); } this._fullRefresh(); } // TODO: Is this useful when we have onResize? public handleCharSizeChanged(): void { - this._renderer?.handleCharSizeChanged(); + this._renderer.value?.handleCharSizeChanged(); } public handleBlur(): void { - this._renderer?.handleBlur(); + this._renderer.value?.handleBlur(); } public handleFocus(): void { - this._renderer?.handleFocus(); + this._renderer.value?.handleFocus(); } public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { this._selectionState.start = start; this._selectionState.end = end; this._selectionState.columnSelectMode = columnSelectMode; - this._renderer?.handleSelectionChanged(start, end, columnSelectMode); + this._renderer.value?.handleSelectionChanged(start, end, columnSelectMode); } public handleCursorMove(): void { - this._renderer?.handleCursorMove(); + this._renderer.value?.handleCursorMove(); } public clear(): void { - this._renderer?.clear(); + this._renderer.value?.clear(); } } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 89d9f5e5..47f77406 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { Disposable, toDisposable } from 'common/Lifecycle'; +import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle'; import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; @@ -57,7 +57,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; - private _windowsWrappingHeuristics: IDisposable | undefined; + private _windowsWrappingHeuristics = this.register(new MutableDisposable()); private readonly _onBinary = this.register(new EventEmitter()); public readonly onBinary = this._onBinary.event; @@ -144,11 +144,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Setup WriteBuffer this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult))); this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed)); - - this.register(toDisposable(() => { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; - })); } public write(data: string | Uint8Array, callback?: () => void): void { @@ -267,20 +262,19 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { if (value) { this._enableWindowsWrappingHeuristics(); } else { - this._windowsWrappingHeuristics?.dispose(); - this._windowsWrappingHeuristics = undefined; + this._windowsWrappingHeuristics.clear(); } } protected _enableWindowsWrappingHeuristics(): void { - if (!this._windowsWrappingHeuristics) { + if (!this._windowsWrappingHeuristics.value) { const disposables: IDisposable[] = []; disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); disposables.push(this.registerCsiHandler({ final: 'H' }, () => { updateWindowsModeWrappedState(this._bufferService); return false; })); - this._windowsWrappingHeuristics = toDisposable(() => { + this._windowsWrappingHeuristics.value = toDisposable(() => { for (const d of disposables) { d.dispose(); } diff --git a/src/common/Lifecycle.ts b/src/common/Lifecycle.ts index 659403de..6e5ef27d 100644 --- a/src/common/Lifecycle.ts +++ b/src/common/Lifecycle.ts @@ -13,9 +13,6 @@ export abstract class Disposable implements IDisposable { protected _disposables: IDisposable[] = []; protected _isDisposed: boolean = false; - constructor() { - } - /** * Disposes the object, triggering the `dispose` method on all registered IDisposables. */ diff --git a/src/common/public/AddonManager.ts b/src/common/public/AddonManager.ts index 06c78121..af04a269 100644 --- a/src/common/public/AddonManager.ts +++ b/src/common/public/AddonManager.ts @@ -14,9 +14,6 @@ export interface ILoadedAddon { export class AddonManager implements IDisposable { protected _addons: ILoadedAddon[] = []; - constructor() { - } - public dispose(): void { for (let i = this._addons.length - 1; i >= 0; i--) { this._addons[i].instance.dispose(); diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 5eeeb361..b018d37c 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -182,7 +182,7 @@ export class Terminal extends Disposable implements ITerminalApi { } public loadAddon(addon: ITerminalAddon): void { // TODO: This could cause issues if the addon calls renderer apis - return this._addonManager.loadAddon(this as any, addon); + this._addonManager.loadAddon(this as any, addon); } private _verifyIntegers(...values: number[]): void { diff --git a/webpack.config.headless.js b/webpack.config.headless.js index d5bb97b7..9e9099cd 100644 --- a/webpack.config.headless.js +++ b/webpack.config.headless.js @@ -10,8 +10,10 @@ const path = require('path'); * from tsc (via `yarn watch` or `yarn prebuild`) which are put into `out/` and webpacks them into a * production mode umd library module in `lib-headless/`. The aliases are used fix up the absolute * paths output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`. + * + * @type {import('webpack').Configuration} */ -module.exports = { +const config = { entry: './out/headless/public/Terminal.js', devtool: 'source-map', module: { @@ -41,3 +43,4 @@ module.exports = { }, mode: 'production' }; +module.exports = config; diff --git a/webpack.config.js b/webpack.config.js index 4f9087b5..ca7c059a 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,8 @@ * @license MIT */ +// @ts-check + const path = require('path'); /** @@ -10,8 +12,10 @@ const path = require('path'); * (via `yarn watch` or `yarn prebuild`) which are put into `out/` and webpacks them into a * production mode umd library module in `lib/`. The aliases are used fix up the absolute paths * output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`. + * + * @type {import('webpack').Configuration} */ -module.exports = { +const config = { entry: './out/browser/public/Terminal.js', devtool: 'source-map', module: { @@ -39,3 +43,4 @@ module.exports = { }, mode: 'production' }; +module.exports = config; diff --git a/yarn.lock b/yarn.lock index cde76d90..4b3a34ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -357,6 +357,14 @@ resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.5.tgz#71b6b3ad55061ad02e4a75e909b0c5fe776ae12c" integrity sha512-uJsNeY7Jwci2yDpjx0b99Vb7KOxAI7kgz7L7a19bXZMRFEhGSj0SZkGYg9nGgq+Zrp9nzEe+ceZRY68yIKqA5Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/chai@^4.2.22": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.5.tgz#ae69bcbb1bebb68c4ac0b11e9d8ed04526b3562b" @@ -367,6 +375,13 @@ resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.1.tgz#a0ae06290284f7abebb90a2ddc0187de6d22e963" integrity sha512-m3+6WWfSSl6zqoXy8uQQifbgqV7Gt6fsyWnHLgUWVtJQk75+OfUB+edSZ52YDj7leSiZtX7w1/E4w2x/Hb0orA== +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.7": version "4.1.8" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" @@ -400,6 +415,35 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.17.36" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz#baa9022119bdc05a4adfe740ffc97b5f9360e545" + integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express-ws@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.1.tgz#6fbf5dfdbeedd16479ccbeecbca63c14be26612e" + integrity sha512-VguRXzcpPBF0IggIGpUoM65cZJDfMQxoc6dKoCz1yLzcwcXW7ft60yhq3ygKhyEhEIQFtLrWjyz4AJ1qjmzCFw== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core" "*" + "@types/ws" "*" + +"@types/express@*", "@types/express@4": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -408,6 +452,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/http-errors@*": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" + integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== + "@types/jsdom@^16.2.13": version "16.2.15" resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.15.tgz#6c09990ec43b054e49636cba4d11d54367fc90d6" @@ -429,6 +478,16 @@ dependencies: decimal.js "^10.0.0" +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" @@ -476,11 +535,38 @@ dependencies: "@types/node" "*" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/semver@^7.5.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.2" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.2.tgz#3e5419ecd1e40e7405d34093f10befb43f63381a" + integrity sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" @@ -500,7 +586,7 @@ tapable "^2.2.0" webpack "^5" -"@types/ws@^8.2.0": +"@types/ws@*", "@types/ws@^8.2.0": version "8.5.5" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==