Merge pull request #4185 from Tyriar/4167_register_emitter

Fix a bunch of memory retention problems
This commit is contained in:
Daniel Imms
2022-10-08 10:35:04 -07:00
committed by GitHub
41 changed files with 279 additions and 322 deletions
@@ -19,8 +19,9 @@ import { ICellData } from 'common/Types';
import { Terminal } from 'xterm';
import { IRenderLayer } from './Types';
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
import { Disposable, toDisposable } from 'common/Lifecycle';
export abstract class BaseRenderLayer implements IRenderLayer {
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx!: CanvasRenderingContext2D;
private _scaledCharWidth: number = 0;
@@ -51,6 +52,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected readonly _decorationService: IDecorationService,
protected readonly _coreBrowserService: ICoreBrowserService
) {
super();
this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._selectionModel, this._decorationService, this._coreBrowserService);
this._canvas = document.createElement('canvas');
this._canvas.classList.add(`xterm-${id}-layer`);
@@ -58,11 +60,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._initCanvas();
this._container.appendChild(this._canvas);
this._refreshCharAtlas(this._colors);
}
public dispose(): void {
removeElementFromParent(this._canvas);
this._charAtlas?.dispose();
this.register(toDisposable(() => {
removeElementFromParent(this._canvas);
this._charAtlas?.dispose();
}));
}
private _initCanvas(): void {
+17 -18
View File
@@ -9,20 +9,26 @@ import { CanvasRenderer } from './CanvasRenderer';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ITerminalAddon, Terminal } from 'xterm';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
export class CanvasAddon implements ITerminalAddon {
export class CanvasAddon extends Disposable implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: CanvasRenderer;
private readonly _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
}
public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
if (!terminal.element) {
core.onWillOpen(() => this.activate(terminal));
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
this._terminal = terminal;
const bufferService: IBufferService = core._bufferService;
const renderService: IRenderService = core._renderService;
@@ -35,24 +41,17 @@ export class CanvasAddon implements ITerminalAddon {
const colors: IColorSet = core._colorManager.colors;
const screenElement: HTMLElement = core.screenElement;
const linkifier = core.linkifier2;
this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
renderService.onResize(bufferService.cols, bufferService.rows);
}
public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose CanvasAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
this.register(toDisposable(() => {
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(terminal.cols, terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
}));
}
}
@@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/rende
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IColorSet, ILinkifier2 } from 'browser/Types';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { Terminal } from 'xterm';
import { CursorRenderLayer } from './CursorRenderLayer';
@@ -24,9 +24,9 @@ export class CanvasRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
private readonly _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
private readonly _onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>());
public readonly onRequestRedraw = this._onRequestRedraw.event;
private readonly _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
constructor(
@@ -70,14 +70,13 @@ export class CanvasRenderer extends Disposable implements IRenderer {
this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
this.onOptionsChanged();
}
public dispose(): void {
for (const l of this._renderLayers) {
l.dispose();
}
super.dispose();
removeTerminalFromCache(this._terminal);
this.register(toDisposable(() => {
for (const l of this._renderLayers) {
l.dispose();
}
removeTerminalFromCache(this._terminal);
}));
}
public get textureAtlas(): HTMLCanvasElement | undefined {
@@ -12,6 +12,7 @@ import { IBufferService, IOptionsService, ICoreService, IDecorationService } fro
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
import { Terminal } from 'xterm';
import { toDisposable } from 'common/Lifecycle';
interface ICursorState {
x: number;
@@ -57,14 +58,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
}
public dispose(): void {
if (this._cursorBlinkStateManager) {
this._cursorBlinkStateManager.dispose();
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
}
super.dispose();
}));
}
public resize(dim: IRenderDimensions): void {
@@ -28,8 +28,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
) {
super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService);
linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
this.register(linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)));
this.register(linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)));
}
public resize(dim: IRenderDimensions): void {
@@ -293,19 +293,4 @@ export class TextRenderLayer extends BaseRenderLayer {
this._characterOverlapCache[chars] = overlaps;
return overlaps;
}
/**
* Clear the charcater at the cell specified.
* @param x The column of the char.
* @param y The row of the char.
*/
// private _clearChar(x: number, y: number): void {
// let colsToClear = 1;
// // Clear the adjacent character if it was wide
// const state = this._state.cache[x][y];
// if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
// colsToClear = 2;
// }
// this.clearCells(x, y, colsToClear, 1);
// }
}
-1
View File
@@ -41,7 +41,6 @@ export interface IRenderer extends IDisposable {
*/
readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
dispose(): void;
setColors(colors: IColorSet): void;
onDevicePixelRatioChange(): void;
onResize(cols: number, rows: number): void;
+10 -10
View File
@@ -5,6 +5,7 @@
import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
export interface ISearchOptions {
regex?: boolean;
@@ -50,7 +51,7 @@ type LineCacheEntry = [
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
export class SearchAddon implements ITerminalAddon {
export class SearchAddon extends Disposable implements ITerminalAddon {
private _terminal: Terminal | undefined;
private _cachedSearchTerm: string | undefined;
private _selectedDecoration: IDecoration | undefined;
@@ -72,13 +73,18 @@ export class SearchAddon implements ITerminalAddon {
private _resultIndex: number | undefined;
private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>();
private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>());
public readonly onDidChangeResults = this._onDidChangeResults.event;
public activate(terminal: Terminal): void {
this._terminal = terminal;
this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches());
this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
this._onDataDisposable = this.register(this._terminal.onWriteParsed(() => this._updateMatches()));
this._onResizeDisposable = this.register(this._terminal.onResize(() => this._updateMatches()));
this.register(toDisposable(() => {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}));
}
private _updateMatches(): void {
@@ -94,12 +100,6 @@ export class SearchAddon implements ITerminalAddon {
}
}
public dispose(): void {
this.clearDecorations();
this._onDataDisposable?.dispose();
this._onResizeDisposable?.dispose();
}
public clearDecorations(retainCachedSearchTerm?: boolean): void {
this._selectedDecoration?.dispose();
this._searchResults?.clear();
@@ -76,7 +76,7 @@ let $glyph: IRasterizedGlyph | undefined = undefined;
let $leftCellPadding = 0;
let $clippedPixels = 0;
export class GlyphRenderer extends Disposable {
export class GlyphRenderer extends Disposable {
private _atlas: ITextureAtlas | undefined;
private _program: WebGLProgram;
@@ -99,7 +99,6 @@ export class GlyphRenderer extends Disposable {
constructor(
private _terminal: Terminal,
private _colors: IColorSet,
private _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions
) {
+20 -22
View File
@@ -10,29 +10,32 @@ import { IColorSet } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
export class WebglAddon implements ITerminalAddon {
export class WebglAddon extends Disposable implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;
private readonly _onChangeTextureAtlas = new EventEmitter<HTMLElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
private readonly _onContextLoss = new EventEmitter<void>();
private readonly _onContextLoss = this.register(new EventEmitter<void>());
public readonly onContextLoss = this._onContextLoss.event;
constructor(
private _preserveDrawingBuffer?: boolean
) {}
) {
super();
}
public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
if (!terminal.element) {
core.onWillOpen(() => this.activate(terminal));
return;
}
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
const core = (terminal as any)._core;
if (!terminal.element) {
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
this._terminal = terminal;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
@@ -40,21 +43,16 @@ export class WebglAddon implements ITerminalAddon {
const coreService: ICoreService = core.coreService;
const decorationService: IDecorationService = core._decorationService;
const colors: IColorSet = core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer);
forwardEvent(this._renderer.onContextLoss, this._onContextLoss);
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
this._renderer = this.register(new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer));
this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss));
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
}
public dispose(): void {
if (!this._terminal) {
throw new Error('Cannot dispose WebglAddon because it is activated');
}
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer?.dispose();
this._renderer = undefined;
this.register(toDisposable(() => {
const renderService: IRenderService = (this._terminal as any)._core._renderService;
renderService.setRenderer((this._terminal as any)._core._createRenderer());
renderService.onResize(terminal.cols, terminal.rows);
}));
}
public get textureAtlas(): HTMLCanvasElement | undefined {
+13 -14
View File
@@ -14,7 +14,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { Terminal } from 'xterm';
@@ -46,11 +46,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _isAttached: boolean;
private _contextRestorationTimeout: number | undefined;
private readonly _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
private readonly _onChangeTextureAtlas = this.register(new EventEmitter<HTMLCanvasElement>());
public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event;
private readonly _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
private readonly _onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>());
public readonly onRequestRedraw = this._onRequestRedraw.event;
private readonly _onContextLoss = new EventEmitter<void>();
private readonly _onContextLoss = this.register(new EventEmitter<void>());
public readonly onContextLoss = this._onContextLoss.event;
constructor(
@@ -131,15 +131,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._initializeWebGLState();
this._isAttached = this._coreBrowserService.window.document.body.contains(this._core.screenElement!);
}
public dispose(): void {
for (const l of this._renderLayers) {
l.dispose();
}
this._canvas.parentElement?.removeChild(this._canvas);
removeTerminalFromCache(this._terminal);
super.dispose();
this.register(toDisposable(() => {
for (const l of this._renderLayers) {
l.dispose();
}
this._canvas.parentElement?.removeChild(this._canvas);
removeTerminalFromCache(this._terminal);
}));
}
public get textureAtlas(): HTMLCanvasElement | undefined {
@@ -255,8 +254,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._rectangleRenderer?.dispose();
this._glyphRenderer?.dispose();
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions);
this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions));
this._glyphRenderer = this.register(new GlyphRenderer(this._terminal, this._gl, this.dimensions));
// Update dimensions and acquire char atlas
this.onCharSizeChanged();
@@ -12,8 +12,9 @@ import { ICoreBrowserService } from 'browser/services/Services';
import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types';
import { CellData } from 'common/buffer/CellData';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { Disposable, toDisposable } from 'common/Lifecycle';
export abstract class BaseRenderLayer implements IRenderLayer {
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx!: CanvasRenderingContext2D;
private _scaledCharWidth: number = 0;
@@ -33,18 +34,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected _colors: IColorSet,
protected readonly _coreBrowserService: ICoreBrowserService
) {
super();
this._canvas = document.createElement('canvas');
this._canvas.classList.add(`xterm-${id}-layer`);
this._canvas.style.zIndex = zIndex.toString();
this._initCanvas();
this._container.appendChild(this._canvas);
}
public dispose(): void {
this._canvas.remove();
if (this._charAtlas) {
this._charAtlas.dispose();
}
this.register(toDisposable(() => {
this._canvas.remove();
this._charAtlas?.dispose();
}));
}
private _initCanvas(): void {
@@ -12,6 +12,7 @@ import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService } from 'browser/services/Services';
import { ICoreService } from 'common/services/Services';
import { toDisposable } from 'common/Lifecycle';
interface ICursorState {
x: number;
@@ -55,12 +56,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
'underline': this._renderUnderlineCursor.bind(this)
};
this.onOptionsChanged(terminal);
}
public override dispose(): void {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
super.dispose();
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
}));
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
@@ -10,6 +10,7 @@ import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService } from 'browser/services/Services';
import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
import { toDisposable } from 'common/Lifecycle';
export class LinkRenderLayer extends BaseRenderLayer {
private _state: ILinkifierEvent | undefined;
@@ -23,8 +24,8 @@ export class LinkRenderLayer extends BaseRenderLayer {
) {
super(container, 'link', zIndex, true, colors, coreBrowserService);
terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
this.register(terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)));
this.register(terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)));
}
public resize(terminal: Terminal, dim: IRenderDimensions): void {
+5 -7
View File
@@ -9,7 +9,7 @@ import { IBuffer } from 'common/buffer/Types';
import { isMac } from 'common/Platform';
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { IRenderService } from 'browser/services/Services';
import { removeElementFromParent } from 'browser/Dom';
@@ -104,12 +104,10 @@ export class AccessibilityManager extends Disposable {
// This shouldn't be needed on modern browsers but is present in case the
// media query that drives the ScreenDprMonitor isn't supported
this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));
}
public dispose(): void {
super.dispose();
removeElementFromParent(this._accessibilityTreeRoot);
this._rowElements.length = 0;
this.register(toDisposable(() => {
removeElementFromParent(this._accessibilityTreeRoot);
this._rowElements.length = 0;
}));
}
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
+4 -6
View File
@@ -8,7 +8,7 @@ import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
import { Disposable, getDisposeArrayDisposable, disposeArray, toDisposable } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
export class Linkifier2 extends Disposable implements ILinkifier2 {
@@ -36,11 +36,9 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
) {
super();
this.register(getDisposeArrayDisposable(this._linkCacheDisposables));
}
public dispose(): void {
super.dispose();
this._lastMouseEvent = undefined;
this.register(toDisposable(() => {
this._lastMouseEvent = undefined;
}));
}
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
+4 -6
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void;
@@ -26,6 +26,9 @@ export class ScreenDprMonitor extends Disposable {
constructor(private _parentWindow: Window) {
super();
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
this.register(toDisposable(() => {
this.clearListener();
}));
}
public setListener(listener: ScreenDprListener): void {
@@ -43,11 +46,6 @@ export class ScreenDprMonitor extends Disposable {
this._updateDpr();
}
public dispose(): void {
super.dispose();
this.clearListener();
}
private _updateDpr(): void {
if (!this._outerListener) {
return;
-1
View File
@@ -1378,7 +1378,6 @@ describe('Terminal', () => {
assert.deepEqual(disposeStack, [markers[0], markers[1]]);
// trimmed marker objs should be disposed
assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]);
assert.deepEqual(disposeStack.map(el => (el as any)._isDisposed), [true, true]);
// trimmed markers should contain line -1
assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]);
});
+17 -22
View File
@@ -56,6 +56,7 @@ import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { OscLinkProvider } from 'browser/OscLinkProvider';
import { toDisposable } from 'common/Lifecycle';
// Let it work inside Node.js for automated testing purposes.
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
@@ -122,28 +123,28 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _colorManager: ColorManager | undefined;
private _theme: ITheme | undefined;
private readonly _onCursorMove = new EventEmitter<void>();
private readonly _onCursorMove = this.register(new EventEmitter<void>());
public readonly onCursorMove = this._onCursorMove.event;
private readonly _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
private readonly _onKey = this.register(new EventEmitter<{ key: string, domEvent: KeyboardEvent }>());
public readonly onKey = this._onKey.event;
private readonly _onRender = new EventEmitter<{ start: number, end: number }>();
private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>());
public readonly onRender = this._onRender.event;
private readonly _onSelectionChange = new EventEmitter<void>();
private readonly _onSelectionChange = this.register(new EventEmitter<void>());
public readonly onSelectionChange = this._onSelectionChange.event;
private readonly _onTitleChange = new EventEmitter<string>();
private readonly _onTitleChange = this.register(new EventEmitter<string>());
public readonly onTitleChange = this._onTitleChange.event;
private readonly _onBell = new EventEmitter<void>();
private readonly _onBell = this.register(new EventEmitter<void>());
public readonly onBell = this._onBell.event;
private _onFocus = new EventEmitter<void>();
private _onFocus = this.register(new EventEmitter<void>());
public get onFocus(): IEvent<void> { return this._onFocus.event; }
private _onBlur = new EventEmitter<void>();
private _onBlur = this.register(new EventEmitter<void>());
public get onBlur(): IEvent<void> { return this._onBlur.event; }
private _onA11yCharEmitter = new EventEmitter<string>();
private _onA11yCharEmitter = this.register(new EventEmitter<string>());
public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
private _onA11yTabEmitter = new EventEmitter<number>();
private _onA11yTabEmitter = this.register(new EventEmitter<number>());
public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
private _onWillOpen = new EventEmitter<HTMLElement>();
private _onWillOpen = this.register(new EventEmitter<HTMLElement>());
public get onWillOpen(): IEvent<HTMLElement> { return this._onWillOpen.event; }
/**
@@ -184,6 +185,11 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Setup listeners
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
this.register(toDisposable(() => {
this._customKeyEventHandler = undefined;
this.element?.parentNode?.removeChild(this.element);
}));
}
/**
@@ -235,17 +241,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.viewport?.onThemeChange(this._colorManager.colors);
}
public dispose(): void {
if (this._isDisposed) {
return;
}
super.dispose();
this._renderService?.dispose();
this._customKeyEventHandler = undefined;
this.write = () => { };
this.element?.parentNode?.removeChild(this.element);
}
protected _setup(): void {
super._setup();
@@ -5,7 +5,7 @@
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
export class BufferDecorationRenderer extends Disposable {
@@ -39,12 +39,10 @@ export class BufferDecorationRenderer extends Disposable {
}));
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));
this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));
}
public override dispose(): void {
this._container.remove();
this._decorationElements.clear();
super.dispose();
this.register(toDisposable(() => {
this._container.remove();
this._decorationElements.clear();
}));
}
private _queueRefresh(): void {
@@ -6,7 +6,7 @@
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
// Helper objects to avoid excessive calculation and garbage collection during rendering. These are
@@ -68,6 +68,9 @@ export class OverviewRulerRenderer extends Disposable {
this._registerDecorationListeners();
this._registerBufferChangeListeners();
this._registerDimensionChangeListeners();
this.register(toDisposable(() => {
this._canvas?.remove();
}));
}
/**
@@ -120,11 +123,6 @@ export class OverviewRulerRenderer extends Disposable {
this._queueRefresh(true);
}
public override dispose(): void {
this._canvas?.remove();
super.dispose();
}
private _refreshDrawConstants(): void {
// width
const outerWidth = Math.floor(this._canvas.width / 3);
+8 -10
View File
@@ -6,7 +6,7 @@
import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types';
import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
@@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
public readonly onRequestRedraw = new EventEmitter<IRequestRedrawEvent>().event;
public readonly onRequestRedraw = this.register(new EventEmitter<IRequestRedrawEvent>()).event;
constructor(
private _colors: IColorSet,
@@ -89,16 +89,14 @@ export class DomRenderer extends Disposable implements IRenderer {
this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)));
this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)));
}
public dispose(): void {
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
this.register(toDisposable(() => {
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
// Outside influences such as React unmounts may manipulate the DOM before our disposal.
// https://github.com/xtermjs/xterm.js/issues/2960
removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement);
super.dispose();
// Outside influences such as React unmounts may manipulate the DOM before our disposal.
// https://github.com/xtermjs/xterm.js/issues/2960
removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement);
}));
}
private _updateDimensions(): void {
+5 -3
View File
@@ -4,10 +4,11 @@
*/
import { IOptionsService } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { EventEmitter } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
export class CharSizeService implements ICharSizeService {
export class CharSizeService extends Disposable implements ICharSizeService {
public serviceBrand: undefined;
public width: number = 0;
@@ -16,7 +17,7 @@ export class CharSizeService implements ICharSizeService {
public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
private readonly _onCharSizeChange = new EventEmitter<void>();
private readonly _onCharSizeChange = this.register(new EventEmitter<void>());
public readonly onCharSizeChange = this._onCharSizeChange.event;
constructor(
@@ -24,6 +25,7 @@ export class CharSizeService implements ICharSizeService {
parentElement: HTMLElement,
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
}
+4 -8
View File
@@ -40,13 +40,13 @@ export class RenderService extends Disposable implements IRenderService {
columnSelectMode: false
};
private readonly _onDimensionsChange = new EventEmitter<IRenderDimensions>();
private readonly _onDimensionsChange = this.register(new EventEmitter<IRenderDimensions>());
public readonly onDimensionsChange = this._onDimensionsChange.event;
private readonly _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>();
private readonly _onRenderedViewportChange = this.register(new EventEmitter<{ start: number, end: number }>());
public readonly onRenderedViewportChange = this._onRenderedViewportChange.event;
private readonly _onRender = new EventEmitter<{ start: number, end: number }>();
private readonly _onRender = this.register(new EventEmitter<{ start: number, end: number }>());
public readonly onRender = this._onRender.event;
private readonly _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
private readonly _onRefreshRequest = this.register(new EventEmitter<{ start: number, end: number }>());
public readonly onRefreshRequest = this._onRefreshRequest.event;
public get dimensions(): IRenderDimensions { return this._renderer!.dimensions; }
@@ -169,10 +169,6 @@ export class RenderService extends Disposable implements IRenderService {
this._onDimensionsChange.fire(this._renderer.dimensions);
}
public dispose(): void {
super.dispose();
}
public hasRenderer(): boolean {
return !!this._renderer;
}
+4 -4
View File
@@ -15,7 +15,7 @@ import { IBufferRange, ILinkifier2 } from 'browser/Types';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
import { moveToCellSequence } from 'browser/input/MoveToCell';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { getRangeLength } from 'common/buffer/BufferRange';
/**
@@ -148,10 +148,10 @@ export class SelectionService extends Disposable implements ISelectionService {
this._model = new SelectionModel(this._bufferService);
this._activeSelectionMode = SelectionMode.NORMAL;
}
public dispose(): void {
this._removeMouseDownListeners();
this.register(toDisposable(() => {
this._removeMouseDownListeners();
}));
}
public reset(): void {
+6 -4
View File
@@ -5,6 +5,7 @@
import { ICircularList } from 'common/Types';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
export interface IInsertEvent {
index: number;
@@ -20,21 +21,22 @@ export interface IDeleteEvent {
* Represents a circular list; a list with a maximum size that wraps around when push is called,
* overriding values at the start of the list.
*/
export class CircularList<T> implements ICircularList<T> {
export class CircularList<T> extends Disposable implements ICircularList<T> {
protected _array: (T | undefined)[];
private _startIndex: number;
private _length: number;
public readonly onDeleteEmitter = new EventEmitter<IDeleteEvent>();
public readonly onDeleteEmitter = this.register(new EventEmitter<IDeleteEvent>());
public readonly onDelete = this.onDeleteEmitter.event;
public readonly onInsertEmitter = new EventEmitter<IInsertEvent>();
public readonly onInsertEmitter = this.register(new EventEmitter<IInsertEvent>());
public readonly onInsert = this.onInsertEmitter.event;
public readonly onTrimEmitter = new EventEmitter<number>();
public readonly onTrimEmitter = this.register(new EventEmitter<number>());
public readonly onTrim = this.onTrimEmitter.event;
constructor(
private _maxLength: number
) {
super();
this._array = new Array<T>(this._maxLength);
this._startIndex = 0;
this._length = 0;
+20 -24
View File
@@ -21,7 +21,7 @@
* http://linux.die.net/man/7/urxvt
*/
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, LogLevelEnum, ITerminalOptions, IOscLinkService } from 'common/services/Services';
import { InstantiationService } from 'common/services/InstantiationService';
import { LogService } from 'common/services/LogService';
@@ -59,15 +59,15 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
private _writeBuffer: WriteBuffer;
private _windowsMode: IDisposable | undefined;
private readonly _onBinary = new EventEmitter<string>();
private readonly _onBinary = this.register(new EventEmitter<string>());
public readonly onBinary = this._onBinary.event;
private readonly _onData = new EventEmitter<string>();
private readonly _onData = this.register(new EventEmitter<string>());
public readonly onData = this._onData.event;
protected _onLineFeed = new EventEmitter<void>();
protected _onLineFeed = this.register(new EventEmitter<void>());
public readonly onLineFeed = this._onLineFeed.event;
private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>();
private readonly _onResize = this.register(new EventEmitter<{ cols: number, rows: number }>());
public readonly onResize = this._onResize.event;
protected readonly _onWriteParsed = new EventEmitter<void>();
protected readonly _onWriteParsed = this.register(new EventEmitter<void>());
public readonly onWriteParsed = this._onWriteParsed.event;
/**
@@ -75,13 +75,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
* it's filtered out.
*/
protected _onScrollApi?: EventEmitter<number, void>;
protected _onScroll = new EventEmitter<IScrollEvent, void>();
protected _onScroll = this.register(new EventEmitter<IScrollEvent, void>());
public get onScroll(): IEvent<number, void> {
if (!this._onScrollApi) {
this._onScrollApi = new EventEmitter<number, void>();
this.register(this._onScroll.event(ev => {
this._onScrollApi = this.register(new EventEmitter<number, void>());
this._onScroll.event(ev => {
this._onScrollApi?.fire(ev.position);
}));
});
}
return this._onScrollApi.event;
}
@@ -103,17 +103,17 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
// Setup and initialize services
this._instantiationService = new InstantiationService();
this.optionsService = new OptionsService(options);
this.optionsService = this.register(new OptionsService(options));
this._instantiationService.setService(IOptionsService, this.optionsService);
this._bufferService = this.register(this._instantiationService.createInstance(BufferService));
this._instantiationService.setService(IBufferService, this._bufferService);
this._logService = this._instantiationService.createInstance(LogService);
this._logService = this.register(this._instantiationService.createInstance(LogService));
this._instantiationService.setService(ILogService, this._logService);
this.coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()));
this._instantiationService.setService(ICoreService, this.coreService);
this.coreMouseService = this._instantiationService.createInstance(CoreMouseService);
this.coreMouseService = this.register(this._instantiationService.createInstance(CoreMouseService));
this._instantiationService.setService(ICoreMouseService, this.coreMouseService);
this.unicodeService = this._instantiationService.createInstance(UnicodeService);
this.unicodeService = this.register(this._instantiationService.createInstance(UnicodeService));
this._instantiationService.setService(IUnicodeService, this.unicodeService);
this._charsetService = this._instantiationService.createInstance(CharsetService);
this._instantiationService.setService(ICharsetService, this._charsetService);
@@ -121,7 +121,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this._instantiationService.setService(IOscLinkService, this._oscLinkService);
// Register input handler and handle/forward events
this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService);
this._inputHandler = this.register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService));
this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
this.register(this._inputHandler);
@@ -141,17 +141,13 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
}));
// Setup WriteBuffer
this._writeBuffer = 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));
}
public dispose(): void {
if (this._isDisposed) {
return;
}
super.dispose();
this._windowsMode?.dispose();
this._windowsMode = undefined;
this.register(toDisposable(() => {
this._windowsMode?.dispose();
this._windowsMode = undefined;
}));
}
public write(data: string | Uint8Array, callback?: () => void): void {
+13 -17
View File
@@ -132,32 +132,32 @@ export class InputHandler extends Disposable implements IInputHandler {
private _activeBuffer: IBuffer;
private readonly _onRequestBell = new EventEmitter<void>();
private readonly _onRequestBell = this.register(new EventEmitter<void>());
public readonly onRequestBell = this._onRequestBell.event;
private readonly _onRequestRefreshRows = new EventEmitter<number, number>();
private readonly _onRequestRefreshRows = this.register(new EventEmitter<number, number>());
public readonly onRequestRefreshRows = this._onRequestRefreshRows.event;
private readonly _onRequestReset = new EventEmitter<void>();
private readonly _onRequestReset = this.register(new EventEmitter<void>());
public readonly onRequestReset = this._onRequestReset.event;
private readonly _onRequestSendFocus = new EventEmitter<void>();
private readonly _onRequestSendFocus = this.register(new EventEmitter<void>());
public readonly onRequestSendFocus = this._onRequestSendFocus.event;
private readonly _onRequestSyncScrollBar = new EventEmitter<void>();
private readonly _onRequestSyncScrollBar = this.register(new EventEmitter<void>());
public readonly onRequestSyncScrollBar = this._onRequestSyncScrollBar.event;
private readonly _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
private readonly _onRequestWindowsOptionsReport = this.register(new EventEmitter<WindowsOptionsReportType>());
public readonly onRequestWindowsOptionsReport = this._onRequestWindowsOptionsReport.event;
private readonly _onA11yChar = new EventEmitter<string>();
private readonly _onA11yChar = this.register(new EventEmitter<string>());
public readonly onA11yChar = this._onA11yChar.event;
private readonly _onA11yTab = new EventEmitter<number>();
private readonly _onA11yTab = this.register(new EventEmitter<number>());
public readonly onA11yTab = this._onA11yTab.event;
private readonly _onCursorMove = new EventEmitter<void>();
private readonly _onCursorMove = this.register(new EventEmitter<void>());
public readonly onCursorMove = this._onCursorMove.event;
private readonly _onLineFeed = new EventEmitter<void>();
private readonly _onLineFeed = this.register(new EventEmitter<void>());
public readonly onLineFeed = this._onLineFeed.event;
private readonly _onScroll = new EventEmitter<number>();
private readonly _onScroll = this.register(new EventEmitter<number>());
public readonly onScroll = this._onScroll.event;
private readonly _onTitleChange = new EventEmitter<string>();
private readonly _onTitleChange = this.register(new EventEmitter<string>());
public readonly onTitleChange = this._onTitleChange.event;
private readonly _onColor = new EventEmitter<IColorEvent>();
private readonly _onColor = this.register(new EventEmitter<IColorEvent>());
public readonly onColor = this._onColor.event;
private _parseStack: IParseStack = {
@@ -382,10 +382,6 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DcsHandler((data, params) => this.requestStatusString(data, params)));
}
public dispose(): void {
super.dispose();
}
/**
* Async parse support.
*/
+6 -3
View File
@@ -17,15 +17,18 @@ export abstract class Disposable implements IDisposable {
}
/**
* Disposes the object, triggering the `dispose` method on all registered IDisposables.
* Disposes the object, triggering the `dispose` method on all registered IDisposables. This is a
* readonly property instead of a method to prevent subclasses overriding it which is an easy
* mistake that can introduce memory leaks. If a class extends Disposable, all dispose calls
* should be done via {@link register}.
*/
public dispose(): void {
public readonly dispose = (): void => {
this._isDisposed = true;
for (const d of this._disposables) {
d.dispose();
}
this._disposables.length = 0;
}
};
/**
* Registers a disposable object.
+14 -8
View File
@@ -3,25 +3,25 @@
* @license MIT
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IMarker } from 'common/Types';
import { EventEmitter } from 'common/EventEmitter';
import { disposeArray } from 'common/Lifecycle';
import { IDisposable, IMarker } from 'common/Types';
export class Marker extends Disposable implements IMarker {
export class Marker implements IMarker {
private static _nextId = 1;
private _id: number = Marker._nextId++;
public isDisposed: boolean = false;
private _disposables: IDisposable[] = [];
private _id: number = Marker._nextId++;
public get id(): number { return this._id; }
private readonly _onDispose = new EventEmitter<void>();
private readonly _onDispose = this.register(new EventEmitter<void>());
public readonly onDispose = this._onDispose.event;
constructor(
public line: number
) {
super();
}
public dispose(): void {
@@ -32,6 +32,12 @@ export class Marker extends Disposable implements IMarker {
this.line = -1;
// Emit before super.dispose such that dispose listeners get a change to react
this._onDispose.fire();
super.dispose();
disposeArray(this._disposables);
this._disposables.length = 0;
}
public register<T extends IDisposable>(disposable: T): T {
this._disposables.push(disposable);
return disposable;
}
}
+6 -3
View File
@@ -5,6 +5,7 @@
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
declare const setTimeout: (handler: () => void, timeout?: number) => void;
@@ -33,7 +34,7 @@ const WRITE_TIMEOUT_MS = 12;
*/
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
export class WriteBuffer {
export class WriteBuffer extends Disposable {
private _writeBuffer: (string | Uint8Array)[] = [];
private _callbacks: ((() => void) | undefined)[] = [];
private _pendingData = 0;
@@ -42,10 +43,12 @@ export class WriteBuffer {
private _syncCalls = 0;
private _didUserInput = false;
private readonly _onWriteParsed = new EventEmitter<void>();
private readonly _onWriteParsed = this.register(new EventEmitter<void>());
public readonly onWriteParsed = this._onWriteParsed.event;
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) {
super();
}
public handleUserInput(): void {
this._didUserInput = true;
@@ -87,7 +87,7 @@ class TestEscapeSequenceParser extends EscapeSequenceParser {
}
}
public mockOscParser(): void {
this._oscParser = oscPutParser;
(this as any)._oscParser = oscPutParser;
}
public identifier(id: IFunctionIdentifier): number {
return this._identifier(id);
+10 -13
View File
@@ -5,7 +5,7 @@
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';
import { ParserState, ParserAction } from 'common/parser/Constants';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDisposable } from 'common/Types';
import { fill } from 'common/TypedArrayUtils';
import { Params } from 'common/parser/Params';
@@ -242,8 +242,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
protected _executeHandlers: { [flag: number]: ExecuteHandlerType };
protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
protected _escHandlers: IHandlerCollection<EscHandlerType>;
protected _oscParser: IOscParser;
protected _dcsParser: IDcsParser;
protected readonly _oscParser: IOscParser;
protected readonly _dcsParser: IDcsParser;
protected _errorHandler: (state: IParsingState) => IParsingState;
// fallback handlers
@@ -284,8 +284,13 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
this._executeHandlers = Object.create(null);
this._csiHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscParser = new OscParser();
this._dcsParser = new DcsParser();
this.register(toDisposable(() => {
this._csiHandlers = Object.create(null);
this._executeHandlers = Object.create(null);
this._escHandlers = Object.create(null);
}));
this._oscParser = this.register(new OscParser());
this._dcsParser = this.register(new DcsParser());
this._errorHandler = this._errorHandlerFb;
// swallow 7bit ST (ESC+\)
@@ -338,14 +343,6 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
return res.reverse().join('');
}
public dispose(): void {
this._csiHandlers = Object.create(null);
this._executeHandlers = Object.create(null);
this._escHandlers = Object.create(null);
this._oscParser.dispose();
this._dcsParser.dispose();
}
public setPrintHandler(handler: PrintHandlerType): void {
this._printHandler = handler;
}
+1 -1
View File
@@ -5,7 +5,7 @@
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm';
import { BufferApiView } from 'common/public/BufferApiView';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { EventEmitter } from 'common/EventEmitter';
import { ICoreTerminal } from 'common/Types';
export class BufferNamespaceApi implements IBufferNamespaceApi {
+3 -8
View File
@@ -22,9 +22,9 @@ export class BufferService extends Disposable implements IBufferService {
/** Whether the user is scrolling (locks the scroll position) */
public isUserScrolling: boolean = false;
private readonly _onResize = new EventEmitter<{ cols: number, rows: number }>();
private readonly _onResize = this.register(new EventEmitter<{ cols: number, rows: number }>());
public readonly onResize = this._onResize.event;
private readonly _onScroll = new EventEmitter<number>();
private readonly _onScroll = this.register(new EventEmitter<number>());
public readonly onScroll = this._onScroll.event;
public get buffer(): IBuffer { return this.buffers.active; }
@@ -36,12 +36,7 @@ export class BufferService extends Disposable implements IBufferService {
super();
this.cols = Math.max(optionsService.rawOptions.cols || 0, MINIMUM_COLS);
this.rows = Math.max(optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
this.buffers = new BufferSet(optionsService, this);
}
public dispose(): void {
super.dispose();
this.buffers.dispose();
this.buffers = this.register(new BufferSet(optionsService, this));
}
public resize(cols: number, rows: number): void {
+4 -2
View File
@@ -5,6 +5,7 @@
import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
import { Disposable } from 'common/Lifecycle';
/**
* Supported default protocols.
@@ -165,20 +166,21 @@ const DEFAULT_ENCODINGS: { [key: string]: CoreMouseEncoding } = {
* a tracking report to the backend based on protocol and encoding limitations.
* To send a mouse event call `triggerMouseEvent`.
*/
export class CoreMouseService implements ICoreMouseService {
export class CoreMouseService extends Disposable implements ICoreMouseService {
private _protocols: { [name: string]: ICoreMouseProtocol } = {};
private _encodings: { [name: string]: CoreMouseEncoding } = {};
private _activeProtocol: string = '';
private _activeEncoding: string = '';
private _lastEvent: ICoreMouseEvent | null = null;
private readonly _onProtocolChange = new EventEmitter<CoreMouseEventType>();
private readonly _onProtocolChange = this.register(new EventEmitter<CoreMouseEventType>());
public readonly onProtocolChange = this._onProtocolChange.event;
constructor(
@IBufferService private readonly _bufferService: IBufferService,
@ICoreService private readonly _coreService: ICoreService
) {
super();
// register default protocols and encodings
for (const name of Object.keys(DEFAULT_PROTOCOLS)) this.addProtocol(name, DEFAULT_PROTOCOLS[name]);
for (const name of Object.keys(DEFAULT_ENCODINGS)) this.addEncoding(name, DEFAULT_ENCODINGS[name]);
+19 -17
View File
@@ -5,7 +5,7 @@
import { css } from 'common/Color';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
@@ -32,6 +32,16 @@ export class DecorationService extends Disposable implements IDecorationService
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
constructor() {
super();
this.register(toDisposable(() => {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
}
this.reset();
}));
}
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
if (options.marker.isDisposed) {
return undefined;
@@ -81,25 +91,19 @@ export class DecorationService extends Disposable implements IDecorationService
}
});
}
public dispose(): void {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
}
this.reset();
}
}
class Decoration extends Disposable implements IInternalDecoration {
public readonly marker: IMarker;
public element: HTMLElement | undefined;
public isDisposed: boolean = false;
public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>());
public readonly onRender = this.onRenderEmitter.event;
private readonly _onDispose = this.register(new EventEmitter<void>());
public readonly onDispose = this._onDispose.event;
public get isDisposed(): boolean { return this._isDisposed; }
private _cachedBg: IColor | undefined | null = null;
public get backgroundColorRGB(): IColor | undefined {
if (this._cachedBg === null) {
@@ -132,14 +136,12 @@ class Decoration extends Disposable implements IInternalDecoration {
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
this.options.overviewRulerOptions.position = 'full';
}
}
public override dispose(): void {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this._onDispose.fire();
super.dispose();
this.register(toDisposable(() => {
if (this._isDisposed) {
return;
}
this._onDispose.fire();
}));
}
}
+5 -3
View File
@@ -3,6 +3,7 @@
* @license MIT
*/
import { Disposable } from 'common/Lifecycle';
import { ILogService, IOptionsService, LogLevelEnum } from 'common/services/Services';
type LogType = (message?: any, ...optionalParams: any[]) => void;
@@ -29,7 +30,7 @@ const optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = {
const LOG_PREFIX = 'xterm.js: ';
export class LogService implements ILogService {
export class LogService extends Disposable implements ILogService {
public serviceBrand: any;
public logLevel: LogLevelEnum = LogLevelEnum.OFF;
@@ -37,12 +38,13 @@ export class LogService implements ILogService {
constructor(
@IOptionsService private readonly _optionsService: IOptionsService
) {
super();
this._updateLogLevel();
this._optionsService.onOptionChange(key => {
this.register(this._optionsService.onOptionChange(key => {
if (key === 'logLevel') {
this._updateLogLevel();
}
});
}));
}
private _updateLogLevel(): void {
+4 -2
View File
@@ -7,6 +7,7 @@ import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/S
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { isMac } from 'common/Platform';
import { CursorStyle } from 'common/Types';
import { Disposable } from 'common/Lifecycle';
export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
cols: 80,
@@ -51,16 +52,17 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
export class OptionsService implements IOptionsService {
export class OptionsService extends Disposable implements IOptionsService {
public serviceBrand: any;
public readonly rawOptions: Required<ITerminalOptions>;
public options: Required<ITerminalOptions>;
private readonly _onOptionChange = new EventEmitter<string>();
private readonly _onOptionChange = this.register(new EventEmitter<string>());
public readonly onOptionChange = this._onOptionChange.event;
constructor(options: Partial<ITerminalOptions>) {
super();
// set the default value of each option
const defaultOptions = { ...DEFAULT_OPTIONS };
for (const key in options) {
+4 -1
View File
@@ -6,7 +6,6 @@ import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Servic
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { UnicodeV6 } from 'common/input/UnicodeV6';
export class UnicodeService implements IUnicodeService {
public serviceBrand: any;
@@ -24,6 +23,10 @@ export class UnicodeService implements IUnicodeService {
this._activeProvider = defaultProvider;
}
public dispose(): void {
this._onChange.dispose();
}
public get versions(): string[] {
return Object.keys(this._providers);
}
+5 -13
View File
@@ -32,15 +32,15 @@ export class Terminal extends CoreTerminal {
// TODO: We should remove options once components adopt optionsService
public get options(): Required<IInitializedTerminalOptions> { return this.optionsService.options; }
private readonly _onBell = new EventEmitter<void>();
private readonly _onBell = this.register(new EventEmitter<void>());
public readonly onBell = this._onBell.event;
private readonly _onCursorMove = new EventEmitter<void>();
private readonly _onCursorMove = this.register(new EventEmitter<void>());
public readonly onCursorMove = this._onCursorMove.event;
private readonly _onTitleChange = new EventEmitter<string>();
private readonly _onTitleChange = this.register(new EventEmitter<string>());
public readonly onTitleChange = this._onTitleChange.event;
private readonly _onA11yCharEmitter = new EventEmitter<string>();
private readonly _onA11yCharEmitter = this.register(new EventEmitter<string>());
public readonly onA11yChar = this._onA11yCharEmitter.event;
private readonly _onA11yTabEmitter = new EventEmitter<number>();
private readonly _onA11yTabEmitter = this.register(new EventEmitter<number>());
public readonly onA11yTab = this._onA11yTabEmitter.event;
/**
@@ -71,14 +71,6 @@ export class Terminal extends CoreTerminal {
this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
}
public dispose(): void {
if (this._isDisposed) {
return;
}
super.dispose();
this.write = () => { };
}
/**
* Convenience property to active buffer.
*/