Merge branch 'master' into underline-dotted
This commit is contained in:
@@ -127,6 +127,11 @@
|
|||||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
{ "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/prefer-namespace-keyword": "warn",
|
||||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||||
"@typescript-eslint/quotes": [
|
"@typescript-eslint/quotes": [
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ jobs:
|
|||||||
./addons/xterm-addon-canvas/out-test/* \
|
./addons/xterm-addon-canvas/out-test/* \
|
||||||
./addons/xterm-addon-fit/out/* \
|
./addons/xterm-addon-fit/out/* \
|
||||||
./addons/xterm-addon-fit/out-test/* \
|
./addons/xterm-addon-fit/out-test/* \
|
||||||
./addons/xterm-addon-image/inwasm-builds/out/* \
|
|
||||||
./addons/xterm-addon-image/out/* \
|
./addons/xterm-addon-image/out/* \
|
||||||
./addons/xterm-addon-image/out-test/* \
|
./addons/xterm-addon-image/out-test/* \
|
||||||
./addons/xterm-addon-ligatures/out/* \
|
./addons/xterm-addon-ligatures/out/* \
|
||||||
@@ -73,6 +72,39 @@ jobs:
|
|||||||
- name: Lint API
|
- name: Lint API
|
||||||
run: yarn 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:
|
test-unit-parallel:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -3,24 +3,24 @@
|
|||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ReadonlyColorSet } from 'browser/Types';
|
||||||
|
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
|
||||||
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
|
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
|
||||||
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
||||||
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
||||||
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
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 { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
||||||
|
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
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 { CellData } from 'common/buffer/CellData';
|
||||||
import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { ICellData, IDisposable } from 'common/Types';
|
|
||||||
import { Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { IRenderLayer } from './Types';
|
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 {
|
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
@@ -37,7 +37,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
|||||||
private _bitmapGenerator: (BitmapGenerator | undefined)[] = [];
|
private _bitmapGenerator: (BitmapGenerator | undefined)[] = [];
|
||||||
|
|
||||||
protected _charAtlas!: ITextureAtlas;
|
protected _charAtlas!: ITextureAtlas;
|
||||||
private _charAtlasDisposable?: IDisposable;
|
protected _charAtlasDisposable = this.register(new MutableDisposable());
|
||||||
|
|
||||||
public get canvas(): HTMLCanvasElement { return this._canvas; }
|
public get canvas(): HTMLCanvasElement { return this._canvas; }
|
||||||
public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].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.register(toDisposable(() => {
|
||||||
this._canvas.remove();
|
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) {
|
if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {
|
||||||
return;
|
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._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();
|
this._charAtlas.warmUp();
|
||||||
for (let i = 0; i < this._charAtlas.pages.length; i++) {
|
for (let i = 0; i < this._charAtlas.pages.length; i++) {
|
||||||
this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas);
|
this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStat
|
|||||||
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||||
import { IEventEmitter } from 'common/EventEmitter';
|
import { IEventEmitter } from 'common/EventEmitter';
|
||||||
import { toDisposable } from 'common/Lifecycle';
|
import { MutableDisposable } from 'common/Lifecycle';
|
||||||
import { isFirefox } from 'common/Platform';
|
import { isFirefox } from 'common/Platform';
|
||||||
import { ICellData } from 'common/Types';
|
import { ICellData } from 'common/Types';
|
||||||
import { CellData } from 'common/buffer/CellData';
|
import { CellData } from 'common/buffer/CellData';
|
||||||
@@ -26,7 +26,7 @@ interface ICursorState {
|
|||||||
export class CursorRenderLayer extends BaseRenderLayer {
|
export class CursorRenderLayer extends BaseRenderLayer {
|
||||||
private _state: ICursorState;
|
private _state: ICursorState;
|
||||||
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
|
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
|
||||||
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
|
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this.register(new MutableDisposable());
|
||||||
private _cell: ICellData = new CellData();
|
private _cell: ICellData = new CellData();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -57,10 +57,6 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
|||||||
};
|
};
|
||||||
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||||
this._handleOptionsChanged();
|
this._handleOptionsChanged();
|
||||||
this.register(toDisposable(() => {
|
|
||||||
this._cursorBlinkStateManager?.dispose();
|
|
||||||
this._cursorBlinkStateManager = undefined;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public resize(dim: IRenderDimensions): void {
|
public resize(dim: IRenderDimensions): void {
|
||||||
@@ -77,28 +73,27 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
|||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this._clearCursor();
|
this._clearCursor();
|
||||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||||
this._handleOptionsChanged();
|
this._handleOptionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleBlur(): void {
|
public handleBlur(): void {
|
||||||
this._cursorBlinkStateManager?.pause();
|
this._cursorBlinkStateManager.value?.pause();
|
||||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleFocus(): void {
|
public handleFocus(): void {
|
||||||
this._cursorBlinkStateManager?.resume();
|
this._cursorBlinkStateManager.value?.resume();
|
||||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleOptionsChanged(): void {
|
private _handleOptionsChanged(): void {
|
||||||
if (this._optionsService.rawOptions.cursorBlink) {
|
if (this._optionsService.rawOptions.cursorBlink) {
|
||||||
if (!this._cursorBlinkStateManager) {
|
if (!this._cursorBlinkStateManager.value) {
|
||||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
|
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._cursorBlinkStateManager?.dispose();
|
this._cursorBlinkStateManager.clear();
|
||||||
this._cursorBlinkStateManager = undefined;
|
|
||||||
}
|
}
|
||||||
// Request a refresh from the terminal as management of rendering is being
|
// Request a refresh from the terminal as management of rendering is being
|
||||||
// moved back to the terminal
|
// moved back to the terminal
|
||||||
@@ -106,14 +101,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public handleCursorMove(): void {
|
public handleCursorMove(): void {
|
||||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleGridChanged(startRow: number, endRow: number): void {
|
public handleGridChanged(startRow: number, endRow: number): void {
|
||||||
if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {
|
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) {
|
||||||
this._render(false);
|
this._render(false);
|
||||||
} else {
|
} 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
|
// 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();
|
this._clearCursor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ const MINIMUM_ROWS = 1;
|
|||||||
export class FitAddon implements ITerminalAddon {
|
export class FitAddon implements ITerminalAddon {
|
||||||
private _terminal: Terminal | undefined;
|
private _terminal: Terminal | undefined;
|
||||||
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
public activate(terminal: Terminal): void {
|
public activate(terminal: Terminal): void {
|
||||||
this._terminal = terminal;
|
this._terminal = terminal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ describe('FitAddon', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('proposeDimensions', () => {
|
describe('proposeDimensions', () => {
|
||||||
afterEach(async () => {
|
afterEach(() => unloadFit());
|
||||||
return await unloadFit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('default', async function(): Promise<any> {
|
it('default', async function(): Promise<any> {
|
||||||
await loadFit();
|
await loadFit();
|
||||||
@@ -82,9 +80,7 @@ describe('FitAddon', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('fit', () => {
|
describe('fit', () => {
|
||||||
afterEach(async () => {
|
afterEach(() => unloadFit());
|
||||||
return await unloadFit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('default', async function(): Promise<any> {
|
it('default', async function(): Promise<any> {
|
||||||
await loadFit();
|
await loadFit();
|
||||||
|
|||||||
@@ -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":""}
|
|
||||||
Binary file not shown.
@@ -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)))
|
|
||||||
@@ -17,8 +17,7 @@
|
|||||||
"xterm.js"
|
"xterm.js"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"inwasm": "inwasm out/*.wasm.js",
|
"prepackage": "../../node_modules/.bin/tsc -p .",
|
||||||
"prepackage": "../../node_modules/.bin/tsc -p . && inwasm -f out/*.wasm.js",
|
|
||||||
"package": "../../node_modules/.bin/webpack",
|
"package": "../../node_modules/.bin/webpack",
|
||||||
"prepublishOnly": "npm run package"
|
"prepublishOnly": "npm run package"
|
||||||
},
|
},
|
||||||
@@ -26,7 +25,7 @@
|
|||||||
"xterm": "^5.2.0"
|
"xterm": "^5.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"inwasm": "^0.0.13",
|
"sixel": "^0.16.0",
|
||||||
"sixel": "^0.16.0"
|
"xterm-wasm-parts": "^0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';
|
import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';
|
||||||
import { ImageRenderer } from './ImageRenderer';
|
import { ImageRenderer } from './ImageRenderer';
|
||||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
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 { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
|
||||||
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { toRGBA8888 } from 'sixel/lib/Colors';
|
import { toRGBA8888 } from 'sixel/lib/Colors';
|
||||||
import { IDisposable } from 'xterm';
|
import { IDisposable } from 'xterm';
|
||||||
import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
|
import { ICellSize, ITerminalExt, IImageSpec, IRenderDimensions, IRenderService } from './Types';
|
||||||
|
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||||
|
|
||||||
|
|
||||||
const PLACEHOLDER_LENGTH = 4096;
|
const PLACEHOLDER_LENGTH = 4096;
|
||||||
@@ -17,12 +18,12 @@ const PLACEHOLDER_HEIGHT = 24;
|
|||||||
* - add canvas layer to DOM (browser only for now)
|
* - add canvas layer to DOM (browser only for now)
|
||||||
* - draw image tiles onRender
|
* - draw image tiles onRender
|
||||||
*/
|
*/
|
||||||
export class ImageRenderer implements IDisposable {
|
export class ImageRenderer extends Disposable implements IDisposable {
|
||||||
public canvas: HTMLCanvasElement | undefined;
|
public canvas: HTMLCanvasElement | undefined;
|
||||||
private _ctx: CanvasRenderingContext2D | null | undefined;
|
private _ctx: CanvasRenderingContext2D | null | undefined;
|
||||||
private _placeholder: HTMLCanvasElement | undefined;
|
private _placeholder: HTMLCanvasElement | undefined;
|
||||||
private _placeholderBitmap: ImageBitmap | undefined;
|
private _placeholderBitmap: ImageBitmap | undefined;
|
||||||
private _optionsRefresh: IDisposable | undefined;
|
private _optionsRefresh = this.register(new MutableDisposable());
|
||||||
private _oldOpen: ((parent: HTMLElement) => void) | undefined;
|
private _oldOpen: ((parent: HTMLElement) => void) | undefined;
|
||||||
private _renderService: IRenderService | undefined;
|
private _renderService: IRenderService | undefined;
|
||||||
private _oldSetRenderer: ((renderer: any) => void) | undefined;
|
private _oldSetRenderer: ((renderer: any) => void) | undefined;
|
||||||
@@ -68,6 +69,7 @@ export class ImageRenderer implements IDisposable {
|
|||||||
|
|
||||||
|
|
||||||
constructor(private _terminal: ITerminalExt) {
|
constructor(private _terminal: ITerminalExt) {
|
||||||
|
super();
|
||||||
this._oldOpen = this._terminal._core.open;
|
this._oldOpen = this._terminal._core.open;
|
||||||
this._terminal._core.open = (parent: HTMLElement): void => {
|
this._terminal._core.open = (parent: HTMLElement): void => {
|
||||||
this._oldOpen?.call(this._terminal._core, parent);
|
this._oldOpen?.call(this._terminal._core, parent);
|
||||||
@@ -77,32 +79,29 @@ export class ImageRenderer implements IDisposable {
|
|||||||
this._open();
|
this._open();
|
||||||
}
|
}
|
||||||
// hack to spot fontSize changes
|
// 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') {
|
if (option === 'fontSize') {
|
||||||
this.rescaleCanvas();
|
this.rescaleCanvas();
|
||||||
this._renderService?.refreshRows(0, this._terminal.rows);
|
this._renderService?.refreshRows(0, this._terminal.rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
this.register(toDisposable(() => {
|
||||||
|
this.removeLayerFromDom();
|
||||||
|
if (this._terminal._core && this._oldOpen) {
|
||||||
public dispose(): void {
|
this._terminal._core.open = this._oldOpen;
|
||||||
this._optionsRefresh?.dispose();
|
this._oldOpen = undefined;
|
||||||
this.removeLayerFromDom();
|
}
|
||||||
if (this._terminal._core && this._oldOpen) {
|
if (this._renderService && this._oldSetRenderer) {
|
||||||
this._terminal._core.open = this._oldOpen;
|
this._renderService.setRenderer = this._oldSetRenderer;
|
||||||
this._oldOpen = undefined;
|
this._oldSetRenderer = undefined;
|
||||||
}
|
}
|
||||||
if (this._renderService && this._oldSetRenderer) {
|
this._renderService = undefined;
|
||||||
this._renderService.setRenderer = this._oldSetRenderer;
|
this.canvas = undefined;
|
||||||
this._oldSetRenderer = undefined;
|
this._ctx = undefined;
|
||||||
}
|
this._placeholderBitmap?.close();
|
||||||
this._renderService = undefined;
|
this._placeholderBitmap = undefined;
|
||||||
this.canvas = undefined;
|
this._placeholder = undefined;
|
||||||
this._ctx = undefined;
|
}));
|
||||||
this._placeholderBitmap?.close();
|
|
||||||
this._placeholderBitmap = undefined;
|
|
||||||
this._placeholder = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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 <wasm_simd128.h>
|
|
||||||
// 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> = Type extends () => IWasmInstance<infer X> ? X : never;
|
|
||||||
type DecodeDefinition = ExtractDefinition<typeof wasmDecode>;
|
|
||||||
|
|
||||||
// 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<DecodeDefinition>;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm';
|
import { Terminal, IDisposable, ITerminalAddon, IDecoration } from 'xterm';
|
||||||
import { EventEmitter } from 'common/EventEmitter';
|
import { EventEmitter } from 'common/EventEmitter';
|
||||||
import { Disposable, toDisposable, disposeArray } from 'common/Lifecycle';
|
import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle';
|
||||||
|
|
||||||
export interface ISearchOptions {
|
export interface ISearchOptions {
|
||||||
regex?: boolean;
|
regex?: boolean;
|
||||||
@@ -66,7 +66,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
|
|||||||
private _cachedSearchTerm: string | undefined;
|
private _cachedSearchTerm: string | undefined;
|
||||||
private _highlightedLines: Set<number> = new Set();
|
private _highlightedLines: Set<number> = new Set();
|
||||||
private _highlightDecorations: IHighlight[] = [];
|
private _highlightDecorations: IHighlight[] = [];
|
||||||
private _selectedDecoration: IHighlight | undefined;
|
private _selectedDecoration: MutableDisposable<IHighlight> = this.register(new MutableDisposable());
|
||||||
private _highlightLimit: number;
|
private _highlightLimit: number;
|
||||||
private _lastSearchOptions: ISearchOptions | undefined;
|
private _lastSearchOptions: ISearchOptions | undefined;
|
||||||
private _highlightTimeout: number | undefined;
|
private _highlightTimeout: number | undefined;
|
||||||
@@ -110,7 +110,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public clearDecorations(retainCachedSearchTerm?: boolean): void {
|
public clearDecorations(retainCachedSearchTerm?: boolean): void {
|
||||||
this.clearActiveDecoration();
|
this._selectedDecoration.clear();
|
||||||
disposeArray(this._highlightDecorations);
|
disposeArray(this._highlightDecorations);
|
||||||
this._highlightDecorations = [];
|
this._highlightDecorations = [];
|
||||||
this._highlightedLines.clear();
|
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
|
* Find the next instance of the term, then scroll to and select it. If it
|
||||||
* doesn't exist, do nothing.
|
* doesn't exist, do nothing.
|
||||||
@@ -320,8 +315,8 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
|
|||||||
private _fireResults(searchOptions?: ISearchOptions): void {
|
private _fireResults(searchOptions?: ISearchOptions): void {
|
||||||
if (searchOptions?.decorations) {
|
if (searchOptions?.decorations) {
|
||||||
let resultIndex = -1;
|
let resultIndex = -1;
|
||||||
if (this._selectedDecoration) {
|
if (this._selectedDecoration.value) {
|
||||||
const selectedMatch = this._selectedDecoration.match;
|
const selectedMatch = this._selectedDecoration.value.match;
|
||||||
for (let i = 0; i < this._highlightDecorations.length; i++) {
|
for (let i = 0; i < this._highlightDecorations.length; i++) {
|
||||||
const match = this._highlightDecorations[i].match;
|
const match = this._highlightDecorations[i].match;
|
||||||
if (match.row === selectedMatch.row && match.col === selectedMatch.col && match.size === selectedMatch.size) {
|
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 {
|
private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {
|
||||||
const terminal = this._terminal!;
|
const terminal = this._terminal!;
|
||||||
this.clearActiveDecoration();
|
this._selectedDecoration.clear();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
terminal.clearSelection();
|
terminal.clearSelection();
|
||||||
return false;
|
return false;
|
||||||
@@ -666,7 +661,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon {
|
|||||||
disposables.push(marker);
|
disposables.push(marker);
|
||||||
disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)));
|
disposables.push(decoration.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true)));
|
||||||
disposables.push(decoration.onDispose(() => disposeArray(disposables)));
|
disposables.push(decoration.onDispose(() => disposeArray(disposables)));
|
||||||
this._selectedDecoration = { decoration, match: result, dispose() { decoration.dispose(); } };
|
this._selectedDecoration.value = { decoration, match: result, dispose() { decoration.dispose(); } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,8 +414,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
|||||||
export class SerializeAddon implements ITerminalAddon {
|
export class SerializeAddon implements ITerminalAddon {
|
||||||
private _terminal: Terminal | undefined;
|
private _terminal: Terminal | undefined;
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
public activate(terminal: Terminal): void {
|
public activate(terminal: Terminal): void {
|
||||||
this._terminal = terminal;
|
this._terminal = terminal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { CellData } from 'common/buffer/CellData';
|
|||||||
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||||
import { traceCall } from 'common/services/LogService';
|
import { traceCall } from 'common/services/LogService';
|
||||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
import { IDisposable, Terminal } from 'xterm';
|
import { Terminal } from 'xterm';
|
||||||
import { GlyphRenderer } from './GlyphRenderer';
|
import { GlyphRenderer } from './GlyphRenderer';
|
||||||
import { RectangleRenderer } from './RectangleRenderer';
|
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';
|
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 {
|
export class WebglRenderer extends Disposable implements IRenderer {
|
||||||
private _renderLayers: IRenderLayer[];
|
private _renderLayers: IRenderLayer[];
|
||||||
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
|
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
|
||||||
private _charAtlasDisposable: IDisposable | undefined;
|
private _charAtlasDisposable = this.register(new MutableDisposable());
|
||||||
private _charAtlas: ITextureAtlas | undefined;
|
private _charAtlas: ITextureAtlas | undefined;
|
||||||
private _devicePixelRatio: number;
|
private _devicePixelRatio: number;
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
|
|
||||||
private _canvas: HTMLCanvasElement;
|
private _canvas: HTMLCanvasElement;
|
||||||
private _gl: IWebGL2RenderingContext;
|
private _gl: IWebGL2RenderingContext;
|
||||||
private _rectangleRenderer?: RectangleRenderer;
|
private _rectangleRenderer: MutableDisposable<RectangleRenderer> = this.register(new MutableDisposable());
|
||||||
private _glyphRenderer?: GlyphRenderer;
|
private _glyphRenderer: MutableDisposable<GlyphRenderer> = this.register(new MutableDisposable());
|
||||||
|
|
||||||
public readonly dimensions: IRenderDimensions;
|
public readonly dimensions: IRenderDimensions;
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
|
|
||||||
this._core.screenElement!.appendChild(this._canvas);
|
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!);
|
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.width = `${this.dimensions.css.canvas.width}px`;
|
||||||
this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`;
|
this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`;
|
||||||
|
|
||||||
this._rectangleRenderer?.setDimensions(this.dimensions);
|
this._rectangleRenderer.value?.setDimensions(this.dimensions);
|
||||||
this._rectangleRenderer?.handleResize();
|
this._rectangleRenderer.value?.handleResize();
|
||||||
this._glyphRenderer?.setDimensions(this.dimensions);
|
this._glyphRenderer.value?.setDimensions(this.dimensions);
|
||||||
this._glyphRenderer?.handleResize();
|
this._glyphRenderer.value?.handleResize();
|
||||||
|
|
||||||
this._refreshCharAtlas();
|
this._refreshCharAtlas();
|
||||||
|
|
||||||
@@ -241,17 +241,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
* Initializes members dependent on WebGL context state.
|
* Initializes members dependent on WebGL context state.
|
||||||
*/
|
*/
|
||||||
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
|
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
|
||||||
// Dispose any previous rectangle and glyph renderers before creating new ones.
|
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
|
||||||
this._rectangleRenderer?.dispose();
|
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions);
|
||||||
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));
|
|
||||||
|
|
||||||
// Update dimensions and acquire char atlas
|
// Update dimensions and acquire char atlas
|
||||||
this.handleCharSizeChanged();
|
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
|
this._coreBrowserService.dpr
|
||||||
);
|
);
|
||||||
if (this._charAtlas !== atlas) {
|
if (this._charAtlas !== atlas) {
|
||||||
this._charAtlasDisposable?.dispose();
|
|
||||||
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
|
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
|
||||||
this._charAtlasDisposable = getDisposeArrayDisposable([
|
this._charAtlasDisposable.value = getDisposeArrayDisposable([
|
||||||
forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
|
forwardEvent(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
|
||||||
forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
|
forwardEvent(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
this._charAtlas = atlas;
|
this._charAtlas = atlas;
|
||||||
this._charAtlas.warmUp();
|
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 {
|
private _clearModel(clearGlyphRenderer: boolean): void {
|
||||||
this._model.clear();
|
this._model.clear();
|
||||||
if (clearGlyphRenderer) {
|
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);
|
l.handleGridChanged(this._terminal, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._glyphRenderer || !this._rectangleRenderer) {
|
if (!this._glyphRenderer.value || !this._rectangleRenderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell renderer the frame is beginning
|
// Tell renderer the frame is beginning
|
||||||
// upon a model clear also refresh the full viewport model
|
// upon a model clear also refresh the full viewport model
|
||||||
// (also triggered by an atlas page merge, part of #4480)
|
// (also triggered by an atlas page merge, part of #4480)
|
||||||
if (this._glyphRenderer.beginFrame()) {
|
if (this._glyphRenderer.value.beginFrame()) {
|
||||||
this._clearModel(true);
|
this._clearModel(true);
|
||||||
this._updateModel(0, this._terminal.rows - 1);
|
this._updateModel(0, this._terminal.rows - 1);
|
||||||
} else {
|
} else {
|
||||||
@@ -356,10 +351,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
this._rectangleRenderer?.renderBackgrounds();
|
this._rectangleRenderer.value.renderBackgrounds();
|
||||||
this._glyphRenderer?.render(this._model);
|
this._glyphRenderer.value.render(this._model);
|
||||||
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isCursorVisible) {
|
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_FG_OFFSET] = this._cellColorResolver.result.fg;
|
||||||
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
|
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) {
|
if (isJoined) {
|
||||||
// Restore work cell
|
// Restore work cell
|
||||||
@@ -511,7 +506,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|||||||
// Null out non-first cells
|
// Null out non-first cells
|
||||||
for (x++; x < lastCharX; x++) {
|
for (x++; x < lastCharX; x++) {
|
||||||
j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
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] = NULL_CELL_CODE;
|
||||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
|
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
|
||||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
|
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) {
|
if (modelUpdated) {
|
||||||
this._rectangleRenderer!.updateBackgrounds(this._model);
|
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
||||||
}
|
}
|
||||||
this._rectangleRenderer!.updateCursor(this._model);
|
this._rectangleRenderer.value!.updateCursor(this._model);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
|||||||
}));
|
}));
|
||||||
this.register(toDisposable(() => {
|
this.register(toDisposable(() => {
|
||||||
this._canvas.remove();
|
this._canvas.remove();
|
||||||
this._charAtlas?.dispose();
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1106,7 +1106,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
|||||||
|
|
||||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||||
await page.evaluate(`
|
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.result = new Uint8Array(4);
|
||||||
window.d = window.term._core._renderService.dimensions;
|
window.d = window.term._core._renderService.dimensions;
|
||||||
window.gl.readPixels(
|
window.gl.readPixels(
|
||||||
@@ -1120,7 +1120,7 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
|||||||
|
|
||||||
async function getCellPixels(col: number, row: number): Promise<number[]> {
|
async function getCellPixels(col: number, row: number): Promise<number[]> {
|
||||||
await page.evaluate(`
|
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.result = new Uint8Array(window.d.device.cell.width * window.d.device.cell.height * 4);
|
||||||
window.d = window.term._core._renderService.dimensions;
|
window.d = window.term._core._renderService.dimensions;
|
||||||
window.gl.readPixels(
|
window.gl.readPixels(
|
||||||
|
|||||||
@@ -44,10 +44,8 @@
|
|||||||
"install-addons": "node ./bin/install-addons.js",
|
"install-addons": "node ./bin/install-addons.js",
|
||||||
"presetup": "npm run install-addons",
|
"presetup": "npm run install-addons",
|
||||||
"setup": "npm run build",
|
"setup": "npm run build",
|
||||||
"postsetup": "npm run inwasm",
|
|
||||||
"prepublishOnly": "npm run package",
|
"prepublishOnly": "npm run package",
|
||||||
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
|
"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": "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-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
|
||||||
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
|
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import { MockBufferService } from 'common/TestUtils.test';
|
|||||||
import { ILink } from 'browser/Types';
|
import { ILink } from 'browser/Types';
|
||||||
|
|
||||||
class TestLinkifier2 extends Linkifier2 {
|
class TestLinkifier2 extends Linkifier2 {
|
||||||
constructor(bufferService: IBufferService) {
|
|
||||||
super(bufferService);
|
|
||||||
}
|
|
||||||
|
|
||||||
public set currentLink(link: any) {
|
public set currentLink(link: any) {
|
||||||
this._currentLink = link;
|
this._currentLink = link;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import { ThemeService } from 'browser/services/ThemeService';
|
|||||||
import { color, rgba } from 'common/Color';
|
import { color, rgba } from 'common/Color';
|
||||||
import { CoreTerminal } from 'common/CoreTerminal';
|
import { CoreTerminal } from 'common/CoreTerminal';
|
||||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
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 * as Browser from 'common/Platform';
|
||||||
import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types';
|
import { ColorRequestType, CoreMouseAction, CoreMouseButton, CoreMouseEventType, IColorEvent, ITerminalOptions, KeyboardResultType, ScrollSource, SpecialColorIndex } from 'common/Types';
|
||||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||||
@@ -118,7 +118,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
public linkifier2: ILinkifier2;
|
public linkifier2: ILinkifier2;
|
||||||
public viewport: IViewport | undefined;
|
public viewport: IViewport | undefined;
|
||||||
private _compositionHelper: ICompositionHelper | undefined;
|
private _compositionHelper: ICompositionHelper | undefined;
|
||||||
private _accessibilityManager: AccessibilityManager | undefined;
|
private _accessibilityManager: MutableDisposable<AccessibilityManager> = this.register(new MutableDisposable());
|
||||||
|
|
||||||
private readonly _onCursorMove = this.register(new EventEmitter<void>());
|
private readonly _onCursorMove = this.register(new EventEmitter<void>());
|
||||||
public readonly onCursorMove = this._onCursorMove.event;
|
public readonly onCursorMove = this._onCursorMove.event;
|
||||||
@@ -252,12 +252,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
|
|
||||||
private _handleScreenReaderModeOptionChange(value: boolean): void {
|
private _handleScreenReaderModeOptionChange(value: boolean): void {
|
||||||
if (value) {
|
if (value) {
|
||||||
if (!this._accessibilityManager && this._renderService) {
|
if (!this._accessibilityManager.value && this._renderService) {
|
||||||
this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this);
|
this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._accessibilityManager?.dispose();
|
this._accessibilityManager.clear();
|
||||||
this._accessibilityManager = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,7 +534,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
|||||||
if (this.options.screenReaderMode) {
|
if (this.options.screenReaderMode) {
|
||||||
// Note that this must be done *after* the renderer is created in order to
|
// Note that this must be done *after* the renderer is created in order to
|
||||||
// ensure the correct order of the dprchange event
|
// 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)));
|
this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
|||||||
this._core.clearTextureAtlas();
|
this._core.clearTextureAtlas();
|
||||||
}
|
}
|
||||||
public loadAddon(addon: ITerminalAddon): void {
|
public loadAddon(addon: ITerminalAddon): void {
|
||||||
return this._addonManager.loadAddon(this, addon);
|
this._addonManager.loadAddon(this, addon);
|
||||||
}
|
}
|
||||||
public static get strings(): ILocalizableStrings {
|
public static get strings(): ILocalizableStrings {
|
||||||
return Strings;
|
return Strings;
|
||||||
|
|||||||
@@ -366,11 +366,11 @@ export const powerlineDefinitions: { [index: string]: IVectorShape } = {
|
|||||||
// Right semi-circle solid
|
// Right semi-circle solid
|
||||||
'\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 },
|
'\u{E0B4}': { d: 'M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.FILL, rightPadding: 1 },
|
||||||
// Right semi-circle line
|
// Right semi-circle line
|
||||||
'\u{E0B5}': { d: 'M0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0', type: VectorType.STROKE, rightPadding: 1 },
|
'\u{E0B5}': { d: 'M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0', type: VectorType.STROKE, rightPadding: 1 },
|
||||||
// Left semi-circle solid
|
// Left semi-circle solid
|
||||||
'\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 },
|
'\u{E0B6}': { d: 'M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.FILL, leftPadding: 1 },
|
||||||
// Left semi-circle line
|
// Left semi-circle line
|
||||||
'\u{E0B7}': { d: 'M1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0', type: VectorType.STROKE, leftPadding: 1 },
|
'\u{E0B7}': { d: 'M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0', type: VectorType.STROKE, leftPadding: 1 },
|
||||||
// Lower left triangle
|
// Lower left triangle
|
||||||
'\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL },
|
'\u{E0B8}': { d: 'M-.5,-.5 L1.5,1.5 L-.5,1.5', type: VectorType.FILL },
|
||||||
// Backslash separator
|
// Backslash separator
|
||||||
|
|||||||
@@ -151,50 +151,48 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
// microtask to ensure it does not interrupt textures that will be rendered in the current
|
// 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
|
// 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.
|
// 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)) {
|
if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) {
|
||||||
queueMicrotask(() => {
|
// Find the set of the largest 4 images, below the maximum size, with the highest
|
||||||
// Find the set of the largest 4 images, below the maximum size, with the highest
|
// percentages used
|
||||||
// percentages used
|
const pagesBySize = this._pages.filter(e => {
|
||||||
const pagesBySize = this._pages.filter(e => {
|
return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE);
|
||||||
return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE);
|
}).sort((a, b) => {
|
||||||
}).sort((a, b) => {
|
if (b.canvas.width !== a.canvas.width) {
|
||||||
if (b.canvas.width !== a.canvas.width) {
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return b.percentageUsed - a.percentageUsed;
|
||||||
// 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);
|
|
||||||
});
|
});
|
||||||
|
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
|
// All new atlas pages are created small as they are highly dynamic
|
||||||
@@ -770,13 +768,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
|
// left in the page. The previous active row will become fixed in the process as it now has a
|
||||||
// fixed height
|
// fixed height
|
||||||
if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) {
|
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
|
// Create the new fixed height row, creating a new page if there isn't enough room on the
|
||||||
// current page
|
// current page
|
||||||
let wasNewPageCreated = false;
|
let wasPageAndRowFound = false;
|
||||||
if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) {
|
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
|
// Find the first page with room to create the new row on
|
||||||
let candidatePage: AtlasPage | undefined;
|
let candidatePage: AtlasPage | undefined;
|
||||||
@@ -789,15 +787,30 @@ export class TextureAtlas implements ITextureAtlas {
|
|||||||
if (candidatePage) {
|
if (candidatePage) {
|
||||||
activePage = candidatePage;
|
activePage = candidatePage;
|
||||||
} else {
|
} else {
|
||||||
// Create a new page if there is no room
|
// Before creating a new atlas page that would trigger a page merge, check if the
|
||||||
const newPage = this._createNewPage();
|
// current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will
|
||||||
activePage = newPage;
|
// improve texture utilization by using the available space before the page is merged
|
||||||
activeRow = newPage.currentRow;
|
// and becomes static.
|
||||||
activeRow.height = rasterizedGlyph.size.y;
|
if (
|
||||||
wasNewPageCreated = true;
|
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
|
// Fix the current row as the new row is being added below
|
||||||
if (activePage.currentRow.height > 0) {
|
if (activePage.currentRow.height > 0) {
|
||||||
activePage.fixedRows.push(activePage.currentRow);
|
activePage.fixedRows.push(activePage.currentRow);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { IRenderDebouncerWithCallback } from 'browser/Types';
|
|||||||
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
|
||||||
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||||
import { EventEmitter } from 'common/EventEmitter';
|
import { EventEmitter } from 'common/EventEmitter';
|
||||||
import { Disposable } from 'common/Lifecycle';
|
import { Disposable, MutableDisposable } from 'common/Lifecycle';
|
||||||
import { DebouncedIdleTask } from 'common/TaskQueue';
|
import { DebouncedIdleTask } from 'common/TaskQueue';
|
||||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ interface ISelectionState {
|
|||||||
export class RenderService extends Disposable implements IRenderService {
|
export class RenderService extends Disposable implements IRenderService {
|
||||||
public serviceBrand: undefined;
|
public serviceBrand: undefined;
|
||||||
|
|
||||||
private _renderer: IRenderer | undefined;
|
private _renderer: MutableDisposable<IRenderer> = this.register(new MutableDisposable());
|
||||||
private _renderDebouncer: IRenderDebouncerWithCallback;
|
private _renderDebouncer: IRenderDebouncerWithCallback;
|
||||||
private _screenDprMonitor: ScreenDprMonitor;
|
private _screenDprMonitor: ScreenDprMonitor;
|
||||||
private _pausedResizeTask = new DebouncedIdleTask();
|
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 }>());
|
private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>());
|
||||||
public readonly onRefreshRequest = this._onRefreshRequest.event;
|
public readonly onRefreshRequest = this._onRefreshRequest.event;
|
||||||
|
|
||||||
public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; }
|
public get dimensions(): IRenderDimensions { return this._renderer.value!.dimensions; }
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _rowCount: number,
|
private _rowCount: number,
|
||||||
@@ -63,8 +63,6 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.register({ dispose: () => this._renderer?.dispose() });
|
|
||||||
|
|
||||||
this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end));
|
this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end));
|
||||||
this.register(this._renderDebouncer);
|
this.register(this._renderDebouncer);
|
||||||
|
|
||||||
@@ -73,7 +71,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
this.register(this._screenDprMonitor);
|
this.register(this._screenDprMonitor);
|
||||||
|
|
||||||
this.register(bufferService.onResize(() => this._fullRefresh()));
|
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(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||||
this.register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged()));
|
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 {
|
private _renderRows(start: number, end: number): void {
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +157,11 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
end = Math.min(end, this._rowCount - 1);
|
end = Math.min(end, this._rowCount - 1);
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
this._renderer.renderRows(start, end);
|
this._renderer.value.renderRows(start, end);
|
||||||
|
|
||||||
// Update selection if needed
|
// Update selection if needed
|
||||||
if (this._needsSelectionRefresh) {
|
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;
|
this._needsSelectionRefresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +179,7 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleOptionsChanged(): void {
|
private _handleOptionsChanged(): void {
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.refreshRows(0, this._rowCount - 1);
|
this.refreshRows(0, this._rowCount - 1);
|
||||||
@@ -189,25 +187,23 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _fireOnCanvasResize(): void {
|
private _fireOnCanvasResize(): void {
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Don't fire the event if the dimensions haven't changed
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
this._onDimensionsChange.fire(this._renderer.dimensions);
|
this._onDimensionsChange.fire(this._renderer.value.dimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hasRenderer(): boolean {
|
public hasRenderer(): boolean {
|
||||||
return !!this._renderer;
|
return !!this._renderer.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setRenderer(renderer: IRenderer): void {
|
public setRenderer(renderer: IRenderer): void {
|
||||||
// TODO: RenderService should be the only one to dispose the renderer
|
this._renderer.value = renderer;
|
||||||
this._renderer?.dispose();
|
this._renderer.value.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
|
||||||
this._renderer = renderer;
|
|
||||||
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
|
|
||||||
|
|
||||||
// Force a refresh
|
// Force a refresh
|
||||||
this._needsSelectionRefresh = true;
|
this._needsSelectionRefresh = true;
|
||||||
@@ -227,10 +223,10 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public clearTextureAtlas(): void {
|
public clearTextureAtlas(): void {
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._renderer.clearTextureAtlas?.();
|
this._renderer.value.clearTextureAtlas?.();
|
||||||
this._fullRefresh();
|
this._fullRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,50 +235,50 @@ export class RenderService extends Disposable implements IRenderService {
|
|||||||
// when devicePixelRatio changes
|
// when devicePixelRatio changes
|
||||||
this._charSizeService.measure();
|
this._charSizeService.measure();
|
||||||
|
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._renderer.handleDevicePixelRatioChange();
|
this._renderer.value.handleDevicePixelRatioChange();
|
||||||
this.refreshRows(0, this._rowCount - 1);
|
this.refreshRows(0, this._rowCount - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleResize(cols: number, rows: number): void {
|
public handleResize(cols: number, rows: number): void {
|
||||||
if (!this._renderer) {
|
if (!this._renderer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._isPaused) {
|
if (this._isPaused) {
|
||||||
this._pausedResizeTask.set(() => this._renderer!.handleResize(cols, rows));
|
this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows));
|
||||||
} else {
|
} else {
|
||||||
this._renderer.handleResize(cols, rows);
|
this._renderer.value.handleResize(cols, rows);
|
||||||
}
|
}
|
||||||
this._fullRefresh();
|
this._fullRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is this useful when we have onResize?
|
// TODO: Is this useful when we have onResize?
|
||||||
public handleCharSizeChanged(): void {
|
public handleCharSizeChanged(): void {
|
||||||
this._renderer?.handleCharSizeChanged();
|
this._renderer.value?.handleCharSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleBlur(): void {
|
public handleBlur(): void {
|
||||||
this._renderer?.handleBlur();
|
this._renderer.value?.handleBlur();
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleFocus(): void {
|
public handleFocus(): void {
|
||||||
this._renderer?.handleFocus();
|
this._renderer.value?.handleFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||||
this._selectionState.start = start;
|
this._selectionState.start = start;
|
||||||
this._selectionState.end = end;
|
this._selectionState.end = end;
|
||||||
this._selectionState.columnSelectMode = columnSelectMode;
|
this._selectionState.columnSelectMode = columnSelectMode;
|
||||||
this._renderer?.handleSelectionChanged(start, end, columnSelectMode);
|
this._renderer.value?.handleSelectionChanged(start, end, columnSelectMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleCursorMove(): void {
|
public handleCursorMove(): void {
|
||||||
this._renderer?.handleCursorMove();
|
this._renderer.value?.handleCursorMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this._renderer?.clear();
|
this._renderer.value?.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* http://linux.die.net/man/7/urxvt
|
* 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 { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';
|
||||||
import { InstantiationService } from 'common/services/InstantiationService';
|
import { InstantiationService } from 'common/services/InstantiationService';
|
||||||
import { LogService } from 'common/services/LogService';
|
import { LogService } from 'common/services/LogService';
|
||||||
@@ -57,7 +57,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
|||||||
|
|
||||||
protected _inputHandler: InputHandler;
|
protected _inputHandler: InputHandler;
|
||||||
private _writeBuffer: WriteBuffer;
|
private _writeBuffer: WriteBuffer;
|
||||||
private _windowsWrappingHeuristics: IDisposable | undefined;
|
private _windowsWrappingHeuristics = this.register(new MutableDisposable());
|
||||||
|
|
||||||
private readonly _onBinary = this.register(new EventEmitter<string>());
|
private readonly _onBinary = this.register(new EventEmitter<string>());
|
||||||
public readonly onBinary = this._onBinary.event;
|
public readonly onBinary = this._onBinary.event;
|
||||||
@@ -144,11 +144,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
|||||||
// Setup WriteBuffer
|
// Setup WriteBuffer
|
||||||
this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)));
|
this._writeBuffer = this.register(new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult)));
|
||||||
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));
|
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 {
|
public write(data: string | Uint8Array, callback?: () => void): void {
|
||||||
@@ -267,20 +262,19 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
|||||||
if (value) {
|
if (value) {
|
||||||
this._enableWindowsWrappingHeuristics();
|
this._enableWindowsWrappingHeuristics();
|
||||||
} else {
|
} else {
|
||||||
this._windowsWrappingHeuristics?.dispose();
|
this._windowsWrappingHeuristics.clear();
|
||||||
this._windowsWrappingHeuristics = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _enableWindowsWrappingHeuristics(): void {
|
protected _enableWindowsWrappingHeuristics(): void {
|
||||||
if (!this._windowsWrappingHeuristics) {
|
if (!this._windowsWrappingHeuristics.value) {
|
||||||
const disposables: IDisposable[] = [];
|
const disposables: IDisposable[] = [];
|
||||||
disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));
|
disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));
|
||||||
disposables.push(this.registerCsiHandler({ final: 'H' }, () => {
|
disposables.push(this.registerCsiHandler({ final: 'H' }, () => {
|
||||||
updateWindowsModeWrappedState(this._bufferService);
|
updateWindowsModeWrappedState(this._bufferService);
|
||||||
return false;
|
return false;
|
||||||
}));
|
}));
|
||||||
this._windowsWrappingHeuristics = toDisposable(() => {
|
this._windowsWrappingHeuristics.value = toDisposable(() => {
|
||||||
for (const d of disposables) {
|
for (const d of disposables) {
|
||||||
d.dispose();
|
d.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ export abstract class Disposable implements IDisposable {
|
|||||||
protected _disposables: IDisposable[] = [];
|
protected _disposables: IDisposable[] = [];
|
||||||
protected _isDisposed: boolean = false;
|
protected _isDisposed: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
|
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ export interface ILoadedAddon {
|
|||||||
export class AddonManager implements IDisposable {
|
export class AddonManager implements IDisposable {
|
||||||
protected _addons: ILoadedAddon[] = [];
|
protected _addons: ILoadedAddon[] = [];
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
for (let i = this._addons.length - 1; i >= 0; i--) {
|
for (let i = this._addons.length - 1; i >= 0; i--) {
|
||||||
this._addons[i].instance.dispose();
|
this._addons[i].instance.dispose();
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export class Terminal extends Disposable implements ITerminalApi {
|
|||||||
}
|
}
|
||||||
public loadAddon(addon: ITerminalAddon): void {
|
public loadAddon(addon: ITerminalAddon): void {
|
||||||
// TODO: This could cause issues if the addon calls renderer apis
|
// 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 {
|
private _verifyIntegers(...values: number[]): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user