Merge branch 'master' into feature/lint-by-putout

This commit is contained in:
Daniel Imms
2022-10-04 09:52:10 -07:00
committed by GitHub
55 changed files with 846 additions and 752 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ describe('ColorManager', () => {
return {data: [0, 0, 0, 0xFF]};
}
});
cm = new ColorManager(document, false);
cm = new ColorManager();
});
describe('constructor', () => {
+14 -81
View File
@@ -80,22 +80,11 @@ export const DEFAULT_ANSI_COLORS = Object.freeze((() => {
*/
export class ColorManager implements IColorManager {
public colors: IColorSet;
private _ctx: CanvasRenderingContext2D;
private _litmusColor: CanvasGradient;
private _contrastCache: IColorContrastCache;
private _restoreColors!: IRestoreColorSet;
constructor(document: Document, public allowTransparency: boolean) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Could not get rendering context');
}
this._ctx = ctx;
this._ctx.globalCompositeOperation = 'copy';
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
constructor() {
this._contrastCache = new ColorContrastCache();
this.colors = {
foreground: DEFAULT_FOREGROUND,
@@ -118,9 +107,6 @@ export class ColorManager implements IColorManager {
case 'minimumContrastRatio':
this._contrastCache.clear();
break;
case 'allowTransparency':
this.allowTransparency = value;
break;
}
}
@@ -132,11 +118,11 @@ export class ColorManager implements IColorManager {
public setTheme(theme: ITheme = {}): void {
this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);
this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION, true);
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR);
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT);
this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION);
this.colors.selectionBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionBackgroundTransparent);
this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent, true);
this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent);
this.colors.selectionInactiveBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionInactiveBackgroundTransparent);
const nullColor: IColor = {
css: '',
@@ -220,69 +206,16 @@ export class ColorManager implements IColorManager {
}
private _parseColor(
css: string | undefined,
fallback: IColor,
allowTransparency: boolean = this.allowTransparency
cssString: string | undefined,
fallback: IColor
): IColor {
if (css === undefined) {
return fallback;
}
// If parsing the value results in failure, then it must be ignored, and the attribute must
// retain its previous value.
// -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
this._ctx.fillStyle = this._litmusColor;
this._ctx.fillStyle = css;
if (typeof this._ctx.fillStyle !== 'string') {
console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);
return fallback;
}
this._ctx.fillRect(0, 0, 1, 1);
const data = this._ctx.getImageData(0, 0, 1, 1).data;
// Check if the printed color was transparent
if (data[3] !== 0xFF) {
if (!allowTransparency) {
// Ideally we'd just ignore the alpha channel, but...
//
// Browsers may not give back exactly the same RGB values we put in, because most/all
// convert the color to a pre-multiplied representation. getImageData converts that back to
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
// on their own.
//
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
// into 0x00000000.
//
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
// pixels that have just been set using putImageData() might be returned to an equivalent
// getImageData() as different values."
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
//
// So let's just use the fallback color in this case instead.
console.warn(
`Color: ${css} is using transparency, but allowTransparency is false. ` +
`Using fallback ${fallback.css}.`
);
return fallback;
if (cssString !== undefined) {
try {
return css.toColor(cssString);
} catch {
// no-op
}
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
// the color value has alpha less than 1.0, and the string is the color value in the CSS rgba()
const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component));
const alpha = Math.round(a * 255);
const rgba: number = channels.toRgba(r, g, b, alpha);
return {
rgba,
css
};
}
return {
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
// if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character
css: this._ctx.fillStyle,
rgba: channels.toRgba(data[0], data[1], data[2], data[3])
};
return fallback;
}
}
+4 -6
View File
@@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent
import { IDisposable } from 'common/Types';
import { IMouseService, IRenderService } from './services/Services';
import { IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
import { addDisposableDomListener } from 'browser/Lifecycle';
@@ -26,10 +26,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;
private _activeLine: number = -1;
private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
private _onHideLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
public readonly onShowLinkUnderline = this.register(initEvent<ILinkifierEvent>());
public readonly onHideLinkUnderline = this.register(initEvent<ILinkifierEvent>());
constructor(
@IBufferService private readonly _bufferService: IBufferService
@@ -343,7 +341,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
const range = link.range;
const scrollOffset = this._bufferService.buffer.ydisp;
const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);
const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;
const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline;
emitter.fire(event);
}
+23 -34
View File
@@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
@@ -122,27 +122,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
private _colorManager: ColorManager | undefined;
private _theme: ITheme | undefined;
private _onCursorMove = new EventEmitter<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
private _onRender = new EventEmitter<{ start: number, end: number }>();
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
private _onSelectionChange = new EventEmitter<void>();
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
private _onBell = new EventEmitter<void>();
public get onBell(): IEvent<void> { return this._onBell.event; }
private _onFocus = new EventEmitter<void>();
public get onFocus(): IEvent<void> { return this._onFocus.event; }
private _onBlur = new EventEmitter<void>();
public get onBlur(): IEvent<void> { return this._onBlur.event; }
private _onA11yCharEmitter = new EventEmitter<string>();
public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
private _onA11yTabEmitter = new EventEmitter<number>();
public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
public readonly onCursorMove = initEvent<void>();
public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>();
public readonly onRender = initEvent<{ start: number, end: number }>();
public readonly onSelectionChange = initEvent<void>();
public readonly onTitleChange = initEvent<string>();
public readonly onBell = initEvent<void>();
public readonly onFocus = initEvent<void>();
public readonly onBlur = initEvent<void>();
public readonly onA11yChar = initEvent<string>();
public readonly onA11yTab = initEvent<number>();
/**
* Creates a new `Terminal` object.
@@ -169,16 +158,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._instantiationService.setService(IDecorationService, this._decorationService);
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this._onBell.fire()));
this.register(this._inputHandler.onRequestBell(() => this.onBell.fire()));
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
this.register(this._inputHandler.onRequestReset(() => this.reset()));
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove));
this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange));
this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar));
this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab));
// Setup listeners
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
@@ -326,7 +315,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.updateCursorStyle(ev);
this.element!.classList.add('focus');
this._showCursor();
this._onFocus.fire();
this.onFocus.fire();
}
/**
@@ -349,7 +338,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.coreService.triggerDataEvent(C0.ESC + '[O');
}
this.element!.classList.remove('focus');
this._onBlur.fire();
this.onBlur.fire();
}
private _syncTextArea(): void {
@@ -502,7 +491,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._instantiationService.setService(ICharSizeService, this._charSizeService);
this._theme = this.options.theme || this._theme;
this._colorManager = new ColorManager(document, this.options.allowTransparency);
this._colorManager = new ColorManager();
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e, this.optionsService.rawOptions[e])));
this._colorManager.setTheme(this._theme);
@@ -512,7 +501,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
const renderer = this._createRenderer();
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
this._instantiationService.setService(IRenderService, this._renderService);
this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
this._compositionView = document.createElement('div');
@@ -552,7 +541,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
));
this._instantiationService.setService(ISelectionService, this._selectionService);
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire()));
this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
this.register(this._selectionService.onLinuxMouseSelection(text => {
// If there's a new selection, put it into the textarea, focus and select it
@@ -1101,7 +1090,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.textarea!.value = '';
}
this._onKey.fire({ key: result.key, domEvent: event });
this.onKey.fire({ key: result.key, domEvent: event });
this._showCursor();
this.coreService.triggerDataEvent(result.key, true);
@@ -1184,7 +1173,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
key = String.fromCharCode(key);
this._onKey.fire({ key, domEvent: ev });
this.onKey.fire({ key, domEvent: ev });
this._showCursor();
this.coreService.triggerDataEvent(key, true);
+10 -10
View File
@@ -4,7 +4,7 @@
*/
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter';
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types';
@@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService {
export class MockCharSizeService implements ICharSizeService {
public serviceBrand: undefined;
public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
public onCharSizeChange: IEvent<void> = new EventEmitter<void>().event;
public onCharSizeChange: IEvent<void> = initEvent<void>();
constructor(public width: number, public height: number) {}
public measure(): void {}
}
@@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService {
export class MockRenderService implements IRenderService {
public serviceBrand: undefined;
public onDimensionsChange: IEvent<IRenderDimensions> = new EventEmitter<IRenderDimensions>().event;
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event;
public onDimensionsChange: IEvent<IRenderDimensions> = initEvent<IRenderDimensions>();
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>();
public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>();
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>();
public dimensions: IRenderDimensions = {
scaledCharWidth: 0,
scaledCharHeight: 0,
@@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService {
public hasSelection: boolean = false;
public selectionStart: [number, number] | undefined;
public selectionEnd: [number, number] | undefined;
public onLinuxMouseSelection = new EventEmitter<string>().event;
public onRequestRedraw = new EventEmitter<ISelectionRedrawRequestEvent>().event;
public onRequestScrollLines = new EventEmitter<ISelectionRequestScrollLinesEvent>().event;
public onSelectionChange = new EventEmitter<void>().event;
public onLinuxMouseSelection = initEvent<string>();
public onRequestRedraw = initEvent<ISelectionRedrawRequestEvent>();
public onRequestScrollLines = initEvent<ISelectionRequestScrollLinesEvent>();
public onSelectionChange = initEvent<void>();
public disable(): void {
throw new Error('Method not implemented.');
}
@@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable {
// outside of viewport
if (decoration.element) {
decoration.element.style.display = 'none';
decoration.onRenderEmitter.fire(decoration.element);
decoration.onRender.fire(decoration.element);
}
} else {
let element = this._decorationElements.get(decoration);
@@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable {
}
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
element.style.display = this._altBufferIsActive ? 'none' : 'block';
decoration.onRenderEmitter.fire(element);
decoration.onRender.fire(element);
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefi
'▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK
// Block elements (0x2594-0x2595)
'▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK
'▔': [{ x: 0, y: 0, w: 8, h: 1 }], // UPPER ONE EIGHTH BLOCK
'▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK
// Terminal graphic characters (0x2596-0x259F)
+2 -2
View File
@@ -10,7 +10,7 @@ import { Disposable } 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';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { color } from 'common/Color';
import { removeElementFromParent } from 'browser/Dom';
@@ -40,7 +40,7 @@ export class DomRenderer extends Disposable implements IRenderer {
public dimensions: IRenderDimensions;
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return new EventEmitter<IRequestRedrawEvent>().event; }
public readonly onRequestRedraw = initEvent<IRequestRedrawEvent>();
constructor(
private _colors: IColorSet,
@@ -166,7 +166,7 @@ export class DomRendererRowFactory {
if (cell.isUnderline()) {
charElement.classList.add(`${UNDERLINE_CLASS}-${cell.extended.underlineStyle}`);
if (charElement.textContent === ' ') {
charElement.innerHTML = '&nbsp;';
charElement.textContent = '\xa0'; // = &nbsp;
}
if (!cell.isUnderlineColorDefault()) {
if (cell.isUnderlineColorRGB()) {
+3 -4
View File
@@ -4,7 +4,7 @@
*/
import { IOptionsService } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
export class CharSizeService implements ICharSizeService {
@@ -16,8 +16,7 @@ export class CharSizeService implements ICharSizeService {
public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
private _onCharSizeChange = new EventEmitter<void>();
public get onCharSizeChange(): IEvent<void> { return this._onCharSizeChange.event; }
public readonly onCharSizeChange = initEvent<void>();
constructor(
document: Document,
@@ -32,7 +31,7 @@ export class CharSizeService implements ICharSizeService {
if (result.width !== this.width || result.height !== this.height) {
this.width = result.width;
this.height = result.height;
this._onCharSizeChange.fire();
this.onCharSizeChange.fire();
}
}
}
+16 -13
View File
@@ -5,13 +5,14 @@
import { IRenderer, IRenderDimensions } from 'browser/renderer/Types';
import { RenderDebouncer } from 'browser/RenderDebouncer';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IColorSet, IRenderDebouncerWithCallback } from 'browser/Types';
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { DebouncedIdleTask } from 'common/TaskQueue';
interface ISelectionState {
start: [number, number] | undefined;
@@ -24,6 +25,7 @@ export class RenderService extends Disposable implements IRenderService {
private _renderDebouncer: IRenderDebouncerWithCallback;
private _screenDprMonitor: ScreenDprMonitor;
private _pausedResizeTask = new DebouncedIdleTask();
private _isPaused: boolean = false;
private _needsFullRefresh: boolean = false;
@@ -37,14 +39,10 @@ export class RenderService extends Disposable implements IRenderService {
columnSelectMode: false
};
private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>();
public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; }
private _onRender = new EventEmitter<{ start: number, end: number }>();
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
public readonly onDimensionsChange = initEvent<IRenderDimensions>();
public readonly onRenderedViewportChange = initEvent<{ start: number, end: number }>();
public readonly onRender = initEvent<{ start: number, end: number }>();
public readonly onRefreshRequest = initEvent<{ start: number, end: number }>();
public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }
@@ -105,6 +103,7 @@ export class RenderService extends Disposable implements IRenderService {
}
if (!this._isPaused && this._needsFullRefresh) {
this._pausedResizeTask.flush();
this.refreshRows(0, this._rowCount - 1);
this._needsFullRefresh = false;
}
@@ -132,9 +131,9 @@ export class RenderService extends Disposable implements IRenderService {
// Fire render event only if it was not a redraw
if (!this._isNextRenderRedrawOnly) {
this._onRenderedViewportChange.fire({ start, end });
this.onRenderedViewportChange.fire({ start, end });
}
this._onRender.fire({ start, end });
this.onRender.fire({ start, end });
this._isNextRenderRedrawOnly = true;
}
@@ -154,7 +153,7 @@ export class RenderService extends Disposable implements IRenderService {
if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) {
return;
}
this._onDimensionsChange.fire(this._renderer.dimensions);
this.onDimensionsChange.fire(this._renderer.dimensions);
}
public dispose(): void {
@@ -204,7 +203,11 @@ export class RenderService extends Disposable implements IRenderService {
}
public onResize(cols: number, rows: number): void {
this._renderer.onResize(cols, rows);
if (this._isPaused) {
this._pausedResizeTask.set(() => this._renderer.onResize(cols, rows));
} else {
this._renderer.onResize(cols, rows);
}
this._fullRefresh();
}
+12 -16
View File
@@ -9,7 +9,7 @@ import { IBufferLine, IDisposable } from 'common/Types';
import * as Browser from 'common/Platform';
import { SelectionModel } from 'browser/selection/SelectionModel';
import { CellData } from 'common/buffer/CellData';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { IMouseService, ISelectionService, IRenderService, ICoreBrowserService } from 'browser/services/Services';
import { IBufferRange, ILinkifier2 } from 'browser/Types';
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
@@ -111,14 +111,10 @@ export class SelectionService extends Disposable implements ISelectionService {
private _oldSelectionStart: [number, number] | undefined = undefined;
private _oldSelectionEnd: [number, number] | undefined = undefined;
private _onLinuxMouseSelection = this.register(new EventEmitter<string>());
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
private _onRedrawRequest = this.register(new EventEmitter<ISelectionRedrawRequestEvent>());
public get onRequestRedraw(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
private _onSelectionChange = this.register(new EventEmitter<void>());
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
private _onRequestScrollLines = this.register(new EventEmitter<ISelectionRequestScrollLinesEvent>());
public get onRequestScrollLines(): IEvent<ISelectionRequestScrollLinesEvent> { return this._onRequestScrollLines.event; }
public readonly onLinuxMouseSelection = this.register(initEvent<string>());
public readonly onRequestRedraw = this.register(initEvent<ISelectionRedrawRequestEvent>());
public readonly onSelectionChange = this.register(initEvent<void>());
public readonly onRequestScrollLines = this.register(initEvent<ISelectionRequestScrollLinesEvent>());
constructor(
private readonly _element: HTMLElement,
@@ -260,7 +256,7 @@ export class SelectionService extends Disposable implements ISelectionService {
this._model.clearSelection();
this._removeMouseDownListeners();
this.refresh();
this._onSelectionChange.fire();
this.onSelectionChange.fire();
}
/**
@@ -279,7 +275,7 @@ export class SelectionService extends Disposable implements ISelectionService {
if (Browser.isLinux && isLinuxMouseSelection) {
const selectionText = this.selectionText;
if (selectionText.length) {
this._onLinuxMouseSelection.fire(this.selectionText);
this.onLinuxMouseSelection.fire(this.selectionText);
}
}
}
@@ -290,7 +286,7 @@ export class SelectionService extends Disposable implements ISelectionService {
*/
private _refresh(): void {
this._refreshAnimationFrame = undefined;
this._onRedrawRequest.fire({
this.onRequestRedraw.fire({
start: this._model.finalSelectionStart,
end: this._model.finalSelectionEnd,
columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN
@@ -358,7 +354,7 @@ export class SelectionService extends Disposable implements ISelectionService {
public selectAll(): void {
this._model.isSelectAllActive = true;
this.refresh();
this._onSelectionChange.fire();
this.onSelectionChange.fire();
}
public selectLines(start: number, end: number): void {
@@ -368,7 +364,7 @@ export class SelectionService extends Disposable implements ISelectionService {
this._model.selectionStart = [0, start];
this._model.selectionEnd = [this._bufferService.cols, end];
this.refresh();
this._onSelectionChange.fire();
this.onSelectionChange.fire();
}
/**
@@ -665,7 +661,7 @@ export class SelectionService extends Disposable implements ISelectionService {
return;
}
if (this._dragScrollAmount) {
this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
this.onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
// Re-evaluate selection
// If the cursor was above or below the viewport, make sure it's at the
// start or end of the viewport respectively. This should only happen when
@@ -743,7 +739,7 @@ export class SelectionService extends Disposable implements ISelectionService {
this._oldSelectionStart = start;
this._oldSelectionEnd = end;
this._oldHasSelection = hasSelection;
this._onSelectionChange.fire();
this.onSelectionChange.fire();
}
private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
+11 -14
View File
@@ -4,7 +4,7 @@
*/
import { ICircularList } from 'common/Types';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { initEvent } from 'common/EventEmitter';
export interface IInsertEvent {
index: number;
@@ -25,12 +25,9 @@ export class CircularList<T> implements ICircularList<T> {
private _startIndex: number;
private _length: number;
public onDeleteEmitter = new EventEmitter<IDeleteEvent>();
public get onDelete(): IEvent<IDeleteEvent> { return this.onDeleteEmitter.event; }
public onInsertEmitter = new EventEmitter<IInsertEvent>();
public get onInsert(): IEvent<IInsertEvent> { return this.onInsertEmitter.event; }
public onTrimEmitter = new EventEmitter<number>();
public get onTrim(): IEvent<number> { return this.onTrimEmitter.event; }
public readonly onDelete = initEvent<IDeleteEvent>();
public readonly onInsert = initEvent<IInsertEvent>();
public readonly onTrim = initEvent<number>();
constructor(
private _maxLength: number
@@ -107,7 +104,7 @@ export class CircularList<T> implements ICircularList<T> {
this._array[this._getCyclicIndex(this._length)] = value;
if (this._length === this._maxLength) {
this._startIndex = ++this._startIndex % this._maxLength;
this.onTrimEmitter.fire(1);
this.onTrim.fire(1);
} else {
this._length++;
}
@@ -123,7 +120,7 @@ export class CircularList<T> implements ICircularList<T> {
throw new Error('Can only recycle when the buffer is full');
}
this._startIndex = ++this._startIndex % this._maxLength;
this.onTrimEmitter.fire(1);
this.onTrim.fire(1);
return this._array[this._getCyclicIndex(this._length - 1)]!;
}
@@ -158,7 +155,7 @@ export class CircularList<T> implements ICircularList<T> {
this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
}
this._length -= deleteCount;
this.onDeleteEmitter.fire({ index: start, amount: deleteCount });
this.onDelete.fire({ index: start, amount: deleteCount });
}
// Add items
@@ -169,7 +166,7 @@ export class CircularList<T> implements ICircularList<T> {
this._array[this._getCyclicIndex(start + i)] = items[i];
}
if (items.length) {
this.onInsertEmitter.fire({ index: start, amount: items.length });
this.onInsert.fire({ index: start, amount: items.length });
}
// Adjust length as needed
@@ -177,7 +174,7 @@ export class CircularList<T> implements ICircularList<T> {
const countToTrim = (this._length + items.length) - this._maxLength;
this._startIndex += countToTrim;
this._length = this._maxLength;
this.onTrimEmitter.fire(countToTrim);
this.onTrim.fire(countToTrim);
} else {
this._length += items.length;
}
@@ -193,7 +190,7 @@ export class CircularList<T> implements ICircularList<T> {
}
this._startIndex += count;
this._length -= count;
this.onTrimEmitter.fire(count);
this.onTrim.fire(count);
}
public shiftElements(start: number, count: number, offset: number): void {
@@ -217,7 +214,7 @@ export class CircularList<T> implements ICircularList<T> {
while (this._length > this._maxLength) {
this._length--;
this._startIndex++;
this.onTrimEmitter.fire(1);
this.onTrim.fire(1);
}
}
} else {
+91 -31
View File
@@ -3,8 +3,14 @@
* @license MIT
*/
import { isNode } from 'common/Platform';
import { IColor, IColorRGB } from 'common/Types';
let $r = 0;
let $g = 0;
let $b = 0;
let $a = 0;
/**
* Helper functions where the source type is "channels" (individual color channels as numbers).
*/
@@ -29,8 +35,8 @@ export namespace channels {
*/
export namespace color {
export function blend(bg: IColor, fg: IColor): IColor {
const a = (fg.rgba & 0xFF) / 255;
if (a === 1) {
$a = (fg.rgba & 0xFF) / 255;
if ($a === 1) {
return {
css: fg.css,
rgba: fg.rgba
@@ -42,11 +48,11 @@ export namespace color {
const bgR = (bg.rgba >> 24) & 0xFF;
const bgG = (bg.rgba >> 16) & 0xFF;
const bgB = (bg.rgba >> 8) & 0xFF;
const r = bgR + Math.round((fgR - bgR) * a);
const g = bgG + Math.round((fgG - bgG) * a);
const b = bgB + Math.round((fgB - bgB) * a);
const css = channels.toCss(r, g, b);
const rgba = channels.toRgba(r, g, b);
$r = bgR + Math.round((fgR - bgR) * $a);
$g = bgG + Math.round((fgG - bgG) * $a);
$b = bgB + Math.round((fgB - bgB) * $a);
const css = channels.toCss($r, $g, $b);
const rgba = channels.toRgba($r, $g, $b);
return { css, rgba };
}
@@ -68,25 +74,25 @@ export namespace color {
export function opaque(color: IColor): IColor {
const rgbaColor = (color.rgba | 0xFF) >>> 0;
const [r, g, b] = rgba.toChannels(rgbaColor);
[$r, $g, $b] = rgba.toChannels(rgbaColor);
return {
css: channels.toCss(r, g, b),
css: channels.toCss($r, $g, $b),
rgba: rgbaColor
};
}
export function opacity(color: IColor, opacity: number): IColor {
const a = Math.round(opacity * 0xFF);
const [r, g, b] = rgba.toChannels(color.rgba);
$a = Math.round(opacity * 0xFF);
[$r, $g, $b] = rgba.toChannels(color.rgba);
return {
css: channels.toCss(r, g, b, a),
rgba: channels.toRgba(r, g, b, a)
css: channels.toCss($r, $g, $b, $a),
rgba: channels.toRgba($r, $g, $b, $a)
};
}
export function multiplyOpacity(color: IColor, factor: number): IColor {
const a = color.rgba & 0xFF;
return opacity(color, (a * factor) / 0xFF);
$a = color.rgba & 0xFF;
return opacity(color, ($a * factor) / 0xFF);
}
export function toColorRGB(color: IColor): IColorRGB {
@@ -98,21 +104,45 @@ export namespace color {
* Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa').
*/
export namespace css {
let $ctx: CanvasRenderingContext2D | undefined;
let $litmusColor: CanvasGradient | undefined;
if (!isNode) {
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d', {
willReadFrequently: true
});
if (ctx) {
$ctx = ctx;
$ctx.globalCompositeOperation = 'copy';
$litmusColor = $ctx.createLinearGradient(0, 0, 1, 1);
}
}
/**
* Converts a css string to an IColor, this should handle all valid CSS color strings and will
* throw if it's invalid. The ideal format to use is `#rrggbb[aa]` as it's the fastest to parse.
*
* Only `#rgb[a]`, `#rrggbb[aa]`, `rgb()` and `rgba()` formats are supported when run in a Node
* environment.
*/
export function toColor(css: string): IColor {
// Formats: #rgb[a] and #rrggbb[aa]
if (css.match(/#[\da-f]{3,8}/i)) {
switch (css.length) {
case 4: { // #rgb
const r = parseInt(css.slice(1, 2).repeat(2), 16);
const g = parseInt(css.slice(2, 3).repeat(2), 16);
const b = parseInt(css.slice(3, 4).repeat(2), 16);
return rgba.toColor(r, g, b);
$r = parseInt(css.slice(1, 2).repeat(2), 16);
$g = parseInt(css.slice(2, 3).repeat(2), 16);
$b = parseInt(css.slice(3, 4).repeat(2), 16);
return rgba.toColor($r, $g, $b);
}
case 5: { // #rgba
const r = parseInt(css.slice(1, 2).repeat(2), 16);
const g = parseInt(css.slice(2, 3).repeat(2), 16);
const b = parseInt(css.slice(3, 4).repeat(2), 16);
const a = parseInt(css.slice(4, 5).repeat(2), 16);
return rgba.toColor(r, g, b, a);
$r = parseInt(css.slice(1, 2).repeat(2), 16);
$g = parseInt(css.slice(2, 3).repeat(2), 16);
$b = parseInt(css.slice(3, 4).repeat(2), 16);
$a = parseInt(css.slice(4, 5).repeat(2), 16);
return rgba.toColor($r, $g, $b, $a);
}
case 7: // #rrggbb
return {
@@ -126,15 +156,45 @@ export namespace css {
};
}
}
// Formats: rgb() or rgba()
const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);
if (rgbaMatch) { // rgb() or rgba()
const r = parseInt(rgbaMatch[1]);
const g = parseInt(rgbaMatch[2]);
const b = parseInt(rgbaMatch[3]);
const a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
return rgba.toColor(r, g, b, a);
if (rgbaMatch) {
$r = parseInt(rgbaMatch[1]);
$g = parseInt(rgbaMatch[2]);
$b = parseInt(rgbaMatch[3]);
$a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
return rgba.toColor($r, $g, $b, $a);
}
throw new Error('css.toColor: Unsupported css format');
// Validate the context is available for canvas-based color parsing
if (!$ctx || !$litmusColor) {
throw new Error('css.toColor: Unsupported css format');
}
// Validate the color using canvas fillStyle
// See https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
$ctx.fillStyle = $litmusColor;
$ctx.fillStyle = css;
if (typeof $ctx.fillStyle !== 'string') {
throw new Error('css.toColor: Unsupported css format');
}
$ctx.fillRect(0, 0, 1, 1);
[$r, $g, $b, $a] = $ctx.getImageData(0, 0, 1, 1).data;
// Validate the color is non-transparent as color hue gets lost when drawn to the canvas
if ($a !== 0xFF) {
throw new Error('css.toColor: Unsupported css format');
}
// Extract the color from the canvas' fillStyle property which exposes the color value in rgba()
// format
// See https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
return {
rgba: channels.toRgba($r, $g, $b, $a),
css
};
}
}
+18 -25
View File
@@ -22,16 +22,15 @@
*/
import { Disposable } from 'common/Lifecycle';
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, 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 { LogService } from 'common/services/LogService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { OptionsService } from 'common/services/OptionsService';
import { IDisposable, IAttributeData, ICoreTerminal, IScrollEvent, ScrollSource } from 'common/Types';
import { CoreService } from 'common/services/CoreService';
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter';
import { CoreMouseService } from 'common/services/CoreMouseService';
import { DirtyRowService } from 'common/services/DirtyRowService';
import { UnicodeService } from 'common/services/UnicodeService';
import { CharsetService } from 'common/services/CharsetService';
import { updateWindowsModeWrappedState } from 'common/WindowsMode';
@@ -49,7 +48,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
protected readonly _bufferService: IBufferService;
protected readonly _logService: ILogService;
protected readonly _charsetService: ICharsetService;
protected readonly _dirtyRowService: IDirtyRowService;
protected readonly _oscLinkService: IOscLinkService;
public readonly coreMouseService: ICoreMouseService;
@@ -61,22 +59,18 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
private _writeBuffer: WriteBuffer;
private _windowsMode: IDisposable | undefined;
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
private _onData = new EventEmitter<string>();
public get onData(): IEvent<string> { return this._onData.event; }
protected _onLineFeed = new EventEmitter<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
protected _onScroll = new EventEmitter<IScrollEvent, void>();
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
protected _onWriteParsed = new EventEmitter<void>();
public readonly onBinary = initEvent<string>();
public readonly onData = initEvent<string>();
public readonly onLineFeed = initEvent<void>();
public readonly onResize = initEvent<{ cols: number, rows: number }>();
public readonly onWriteParsed = initEvent<void>();
/**
* Internally we track the source of the scroll but this is meaningless outside the library so
* it's filtered out.
*/
protected _onScrollApi?: EventEmitter<number, void>;
protected _onScroll = new EventEmitter<IScrollEvent, void>();
public get onScroll(): IEvent<number, void> {
if (!this._onScrollApi) {
this._onScrollApi = new EventEmitter<number, void>();
@@ -114,8 +108,6 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this._instantiationService.setService(ICoreService, this.coreService);
this.coreMouseService = this._instantiationService.createInstance(CoreMouseService);
this._instantiationService.setService(ICoreMouseService, this.coreMouseService);
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
this.unicodeService = this._instantiationService.createInstance(UnicodeService);
this._instantiationService.setService(IUnicodeService, this.unicodeService);
this._charsetService = this._instantiationService.createInstance(CharsetService);
@@ -124,27 +116,28 @@ 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._dirtyRowService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService);
this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
this._inputHandler = 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);
// Setup listeners
this.register(forwardEvent(this._bufferService.onResize, this._onResize));
this.register(forwardEvent(this.coreService.onData, this._onData));
this.register(forwardEvent(this.coreService.onBinary, this._onBinary));
this.register(forwardEvent(this._bufferService.onResize, this.onResize));
this.register(forwardEvent(this.coreService.onData, this.onData));
this.register(forwardEvent(this.coreService.onBinary, this.onBinary));
this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput()));
this.register(this.optionsService.onOptionChange(key => this._updateOptions(key)));
this.register(this._bufferService.onScroll(event => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
this.register(this._inputHandler.onScroll(event => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
// Setup WriteBuffer
this._writeBuffer = 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));
}
public dispose(): void {
+32 -2
View File
@@ -14,13 +14,15 @@ export interface IEvent<T, U = void> {
}
export interface IEventEmitter<T, U = void> {
event: IEvent<T, U>;
fire(arg1: T, arg2: U): void;
dispose(): void;
}
export interface IEventWithEmitter<T, U = void> extends IEventEmitter<T, U>, IEvent<T, U> {
}
export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
private _listeners: IListener<T, U>[] = [];
private readonly _listeners: IListener<T, U>[] = [];
private _event?: IEvent<T, U>;
private _disposed: boolean = false;
@@ -64,6 +66,34 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
}
}
/**
* Creates an object that implements both the {@link IEvent} and {@link IEmitter} interfaces. This
* allows more concise instantiation. The idea is to internally use the combined
* {@link IEventWithEmitter} interface and only expose {@link IEvent} externally.
*
* @example
* ```ts
* public readonly onFoo = initEvent<string>();
* // ...
* onFoo(e => handle(e));
* onFoo.fire('bar');
* ```
*/
export function initEvent<T, U = void>(): IEventWithEmitter<T, U> {
const emitter = new EventEmitter<T, U>();
const event = emitter.event;
Object.defineProperty(event, '_listeners', {
value: (emitter as any)._listeners
});
Object.defineProperty(event, 'fire', {
value: emitter.fire.bind(emitter)
});
Object.defineProperty(event, 'dispose', {
value: emitter.dispose.bind(emitter)
});
return event as any;
}
export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
return from(e => to.fire(e));
}
+6 -12
View File
@@ -11,7 +11,7 @@ import { CellData } from 'common/buffer/CellData';
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { Params } from 'common/parser/Params';
import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';
import { MockCoreService, MockBufferService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService, MockUnicodeService, MockOscLinkService } from 'common/TestUtils.test';
import { IBufferService, ICoreService } from 'common/services/Services';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
@@ -67,7 +67,7 @@ describe('InputHandler', () => {
bufferService.resize(80, 30);
coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService);
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
describe('SL/SR/DECIC/DECDC', () => {
@@ -236,7 +236,7 @@ describe('InputHandler', () => {
describe('setMode', () => {
it('should toggle bracketedPasteMode', () => {
const coreService = new MockCoreService();
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new TestInputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
// Set bracketed paste mode
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(coreService.decPrivateModes.bracketedPasteMode, true);
@@ -258,7 +258,6 @@ describe('InputHandler', () => {
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -305,7 +304,6 @@ describe('InputHandler', () => {
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -356,7 +354,6 @@ describe('InputHandler', () => {
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -394,7 +391,6 @@ describe('InputHandler', () => {
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -445,7 +441,6 @@ describe('InputHandler', () => {
bufferService,
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -572,7 +567,6 @@ describe('InputHandler', () => {
new MockBufferService(80, 30),
new MockCharsetService(),
new MockCoreService(),
new MockDirtyRowService(),
new MockLogService(),
new MockOptionsService(),
new MockOscLinkService(),
@@ -599,7 +593,7 @@ describe('InputHandler', () => {
beforeEach(() => {
bufferService = new MockBufferService(80, 30);
handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
handler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockLogService(), new MockOptionsService(), new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', async () => {
await handler.parseP('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
@@ -796,7 +790,7 @@ describe('InputHandler', () => {
describe('colon notation', () => {
let inputHandler2: TestInputHandler;
beforeEach(() => {
inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
describe('should equal to semicolon', () => {
it('CSI 38:2::50:100:150 m', async () => {
@@ -2278,7 +2272,7 @@ describe('InputHandler - async handlers', () => {
coreService = new CoreService(() => { }, bufferService, new MockLogService(), optionsService);
coreService.onData(data => { console.log(data); });
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockLogService(), optionsService, new MockOscLinkService(), new MockCoreMouseService(), new MockUnicodeService());
});
it('async CUP with CPR check', async () => {
+135 -89
View File
@@ -11,12 +11,12 @@ import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
import { Disposable } from 'common/Lifecycle';
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { IParsingState, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';
import { ICoreService, IBufferService, IOptionsService, ILogService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum, IOscLinkService } from 'common/services/Services';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IBuffer } from 'common/buffer/Types';
@@ -104,6 +104,8 @@ export enum WindowsOptionsReportType {
// create a warning log if an async handler takes longer than the limit (in ms)
const SLOW_ASYNC_LIMIT = 5000;
// Work variables to avoid garbage collection
let $temp = 0;
/**
* The terminal's standard implementation of IInputHandler, this handles all
@@ -120,6 +122,7 @@ export class InputHandler extends Disposable implements IInputHandler {
private _windowTitle = '';
private _iconName = '';
private _currentLinkId?: number;
private _dirtyRowTracker: IDirtyRowTracker;
protected _windowTitleStack: string[] = [];
protected _iconNameStack: string[] = [];
@@ -129,33 +132,20 @@ export class InputHandler extends Disposable implements IInputHandler {
private _activeBuffer: IBuffer;
private _onRequestBell = new EventEmitter<void>();
public get onRequestBell(): IEvent<void> { return this._onRequestBell.event; }
private _onRequestRefreshRows = new EventEmitter<number, number>();
public get onRequestRefreshRows(): IEvent<number, number> { return this._onRequestRefreshRows.event; }
private _onRequestReset = new EventEmitter<void>();
public get onRequestReset(): IEvent<void> { return this._onRequestReset.event; }
private _onRequestSendFocus = new EventEmitter<void>();
public get onRequestSendFocus(): IEvent<void> { return this._onRequestSendFocus.event; }
private _onRequestSyncScrollBar = new EventEmitter<void>();
public get onRequestSyncScrollBar(): IEvent<void> { return this._onRequestSyncScrollBar.event; }
private _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
public get onRequestWindowsOptionsReport(): IEvent<WindowsOptionsReportType> { return this._onRequestWindowsOptionsReport.event; }
public readonly onRequestBell = initEvent<void>();
public readonly onRequestRefreshRows = initEvent<number, number>();
public readonly onRequestReset = initEvent<void>();
public readonly onRequestSendFocus = initEvent<void>();
public readonly onRequestSyncScrollBar = initEvent<void>();
public readonly onRequestWindowsOptionsReport = initEvent<WindowsOptionsReportType>();
private _onA11yChar = new EventEmitter<string>();
public get onA11yChar(): IEvent<string> { return this._onA11yChar.event; }
private _onA11yTab = new EventEmitter<number>();
public get onA11yTab(): IEvent<number> { return this._onA11yTab.event; }
private _onCursorMove = new EventEmitter<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onLineFeed = new EventEmitter<void>();
public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
private _onScroll = new EventEmitter<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
private _onColor = new EventEmitter<IColorEvent>();
public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
public readonly onA11yChar = initEvent<string>();
public readonly onA11yTab = initEvent<number>();
public readonly onCursorMove = initEvent<void>();
public readonly onLineFeed = initEvent<void>();
public readonly onScroll = initEvent<number>();
public readonly onTitleChange = initEvent<string>();
public readonly onColor = initEvent<IColorEvent>();
private _parseStack: IParseStack = {
paused: false,
@@ -169,7 +159,6 @@ export class InputHandler extends Disposable implements IInputHandler {
private readonly _bufferService: IBufferService,
private readonly _charsetService: ICharsetService,
private readonly _coreService: ICoreService,
private readonly _dirtyRowService: IDirtyRowService,
private readonly _logService: ILogService,
private readonly _optionsService: IOptionsService,
private readonly _oscLinkService: IOscLinkService,
@@ -179,6 +168,7 @@ export class InputHandler extends Disposable implements IInputHandler {
) {
super();
this.register(this._parser);
this._dirtyRowTracker = new DirtyRowTracker(this._bufferService);
// Track properties used in performance critical code manually to avoid using slow getters
this._activeBuffer = this._bufferService.buffer;
@@ -459,7 +449,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// Clear the dirty row service so we know which lines changed as a result of parsing
// Important: do not clear between async calls, otherwise we lost pending update information.
if (!wasPaused) {
this._dirtyRowService.clearRange();
this._dirtyRowTracker.clearRange();
}
// process big data in smaller chunks
@@ -489,11 +479,11 @@ export class InputHandler extends Disposable implements IInputHandler {
}
if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) {
this._onCursorMove.fire();
this.onCursorMove.fire();
}
// Refresh any dirty rows accumulated as part of parsing
this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end);
this.onRequestRefreshRows.fire(this._dirtyRowTracker.start, this._dirtyRowTracker.end);
}
public print(data: Uint32Array, start: number, end: number): void {
@@ -507,7 +497,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const curAttr = this._curAttrData;
let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
// handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {
@@ -532,7 +522,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
if (screenReaderMode) {
this._onA11yChar.fire(stringFromCodePoint(code));
this.onA11yChar.fire(stringFromCodePoint(code));
}
if (this._currentLinkId !== undefined) {
this._oscLinkService.addLineToLink(this._currentLinkId, this._activeBuffer.ybase + this._activeBuffer.y);
@@ -635,7 +625,7 @@ export class InputHandler extends Disposable implements IInputHandler {
bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
}
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
/**
@@ -684,7 +674,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* and `ITerminalOptions.bellSound`.
*/
public bell(): boolean {
this._onRequestBell.fire();
this.onRequestBell.fire();
return true;
}
@@ -699,7 +689,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF."
*/
public lineFeed(): boolean {
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
if (this._optionsService.rawOptions.convertEol) {
this._activeBuffer.x = 0;
}
@@ -714,9 +704,9 @@ export class InputHandler extends Disposable implements IInputHandler {
if (this._activeBuffer.x >= this._bufferService.cols) {
this._activeBuffer.x--;
}
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
this._onLineFeed.fire();
this.onLineFeed.fire();
return true;
}
@@ -805,7 +795,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const originalX = this._activeBuffer.x;
this._activeBuffer.x = this._activeBuffer.nextStop();
if (this._optionsService.rawOptions.screenReaderMode) {
this._onA11yTab.fire(this._activeBuffer.x - originalX);
this.onA11yTab.fire(this._activeBuffer.x - originalX);
}
return true;
}
@@ -842,14 +832,14 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.y = this._coreService.decPrivateModes.origin
? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y))
: Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y));
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
/**
* Set absolute cursor position.
*/
private _setCursor(x: number, y: number): void {
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
if (this._coreService.decPrivateModes.origin) {
this._activeBuffer.x = x;
this._activeBuffer.y = this._activeBuffer.scrollTop + y;
@@ -858,7 +848,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.y = y;
}
this._restrictCursor();
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
/**
@@ -1178,16 +1168,16 @@ export class InputHandler extends Disposable implements IInputHandler {
switch (params.params[0]) {
case 0:
j = this._activeBuffer.y;
this._dirtyRowService.markDirty(j);
this._dirtyRowTracker.markDirty(j);
this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0, respectProtect);
for (; j < this._bufferService.rows; j++) {
this._resetBufferLine(j, respectProtect);
}
this._dirtyRowService.markDirty(j);
this._dirtyRowTracker.markDirty(j);
break;
case 1:
j = this._activeBuffer.y;
this._dirtyRowService.markDirty(j);
this._dirtyRowTracker.markDirty(j);
// Deleted front part of line and everything before. This line will no longer be wrapped.
this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect);
if (this._activeBuffer.x + 1 >= this._bufferService.cols) {
@@ -1197,15 +1187,15 @@ export class InputHandler extends Disposable implements IInputHandler {
while (j--) {
this._resetBufferLine(j, respectProtect);
}
this._dirtyRowService.markDirty(0);
this._dirtyRowTracker.markDirty(0);
break;
case 2:
j = this._bufferService.rows;
this._dirtyRowService.markDirty(j - 1);
this._dirtyRowTracker.markDirty(j - 1);
while (j--) {
this._resetBufferLine(j, respectProtect);
}
this._dirtyRowService.markDirty(0);
this._dirtyRowTracker.markDirty(0);
break;
case 3:
// Clear scrollback (everything not in viewport)
@@ -1215,7 +1205,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0);
this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0);
// Force a scroll event to refresh viewport
this._onScroll.fire(0);
this.onScroll.fire(0);
}
break;
}
@@ -1257,7 +1247,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true, respectProtect);
break;
}
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
return true;
}
@@ -1289,7 +1279,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
return true;
}
@@ -1323,7 +1313,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
return true;
}
@@ -1349,7 +1339,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1375,7 +1365,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1395,7 +1385,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1);
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1411,7 +1401,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1);
this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA));
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1443,7 +1433,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1476,7 +1466,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1499,7 +1489,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1522,7 +1512,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
line.isWrapped = false;
}
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
return true;
}
@@ -1544,7 +1534,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._activeBuffer.getNullCell(this._eraseAttrData()),
this._eraseAttrData()
);
this._dirtyRowService.markDirty(this._activeBuffer.y);
this._dirtyRowTracker.markDirty(this._activeBuffer.y);
}
return true;
}
@@ -1846,7 +1836,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
if (this._optionsService.rawOptions.windowOptions.setWinLines) {
this._bufferService.resize(132, this._bufferService.rows);
this._onRequestReset.fire();
this.onRequestReset.fire();
}
break;
case 6:
@@ -1865,7 +1855,7 @@ export class InputHandler extends Disposable implements IInputHandler {
case 66:
this._logService.debug('Serial port requested application keypad.');
this._coreService.decPrivateModes.applicationKeypad = true;
this._onRequestSyncScrollBar.fire();
this.onRequestSyncScrollBar.fire();
break;
case 9: // X10 Mouse
// no release, no motion, no wheel, no modifiers.
@@ -1887,7 +1877,7 @@ export class InputHandler extends Disposable implements IInputHandler {
// focusin: ^[[I
// focusout: ^[[O
this._coreService.decPrivateModes.sendFocus = true;
this._onRequestSendFocus.fire();
this.onRequestSendFocus.fire();
break;
case 1005: // utf8 ext mode mouse - removed in #2507
this._logService.debug('DECSET 1005 not supported (see #2507)');
@@ -1914,8 +1904,8 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1047: // alt screen buffer
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
this._coreService.isCursorInitialized = true;
this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this._onRequestSyncScrollBar.fire();
this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this.onRequestSyncScrollBar.fire();
break;
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
this._coreService.decPrivateModes.bracketedPasteMode = true;
@@ -2084,7 +2074,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
if (this._optionsService.rawOptions.windowOptions.setWinLines) {
this._bufferService.resize(80, this._bufferService.rows);
this._onRequestReset.fire();
this.onRequestReset.fire();
}
break;
case 6:
@@ -2103,7 +2093,7 @@ export class InputHandler extends Disposable implements IInputHandler {
case 66:
this._logService.debug('Switching back to normal keypad.');
this._coreService.decPrivateModes.applicationKeypad = false;
this._onRequestSyncScrollBar.fire();
this.onRequestSyncScrollBar.fire();
break;
case 9: // X10 Mouse
case 1000: // vt200 mouse
@@ -2142,8 +2132,8 @@ export class InputHandler extends Disposable implements IInputHandler {
this.restoreCursor();
}
this._coreService.isCursorInitialized = true;
this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this._onRequestSyncScrollBar.fire();
this.onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
this.onRequestSyncScrollBar.fire();
break;
case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
this._coreService.decPrivateModes.bracketedPasteMode = false;
@@ -2642,7 +2632,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public softReset(params: IParams): boolean {
this._coreService.isCursorHidden = false;
this._onRequestSyncScrollBar.fire();
this.onRequestSyncScrollBar.fire();
this._activeBuffer.scrollTop = 0;
this._activeBuffer.scrollBottom = this._bufferService.rows - 1;
this._curAttrData = DEFAULT_ATTR_DATA.clone();
@@ -2762,11 +2752,11 @@ export class InputHandler extends Disposable implements IInputHandler {
switch (params.params[0]) {
case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t
if (second !== 2) {
this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS);
this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS);
}
break;
case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t
this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS);
this.onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS);
break;
case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t
if (this._bufferService) {
@@ -2856,7 +2846,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public setTitle(data: string): boolean {
this._windowTitle = data;
this._onTitleChange.fire(data);
this.onTitleChange.fire(data);
return true;
}
@@ -2898,7 +2888,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
if (event.length) {
this._onColor.fire(event);
this.onColor.fire(event);
}
return true;
}
@@ -2972,11 +2962,11 @@ export class InputHandler extends Disposable implements IInputHandler {
for (let i = 0; i < slots.length; ++i, ++offset) {
if (offset >= this._specialColors.length) break;
if (slots[i] === '?') {
this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);
this.onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);
} else {
const color = parseColor(slots[i]);
if (color) {
this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);
this.onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);
}
}
}
@@ -3037,7 +3027,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public restoreIndexedColor(data: string): boolean {
if (!data) {
this._onColor.fire([{ type: ColorRequestType.RESTORE }]);
this.onColor.fire([{ type: ColorRequestType.RESTORE }]);
return true;
}
const event: IColorEvent = [];
@@ -3051,7 +3041,7 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
if (event.length) {
this._onColor.fire(event);
this.onColor.fire(event);
}
return true;
}
@@ -3062,7 +3052,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color."
*/
public restoreFgColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]);
this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]);
return true;
}
@@ -3072,7 +3062,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color."
*/
public restoreBgColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]);
this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]);
return true;
}
@@ -3082,7 +3072,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color."
*/
public restoreCursorColor(data: string): boolean {
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]);
this.onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]);
return true;
}
@@ -3109,7 +3099,7 @@ export class InputHandler extends Disposable implements IInputHandler {
public keypadApplicationMode(): boolean {
this._logService.debug('Serial port requested application keypad.');
this._coreService.decPrivateModes.applicationKeypad = true;
this._onRequestSyncScrollBar.fire();
this.onRequestSyncScrollBar.fire();
return true;
}
@@ -3121,7 +3111,7 @@ export class InputHandler extends Disposable implements IInputHandler {
public keypadNumericMode(): boolean {
this._logService.debug('Switching back to normal keypad.');
this._coreService.decPrivateModes.applicationKeypad = false;
this._onRequestSyncScrollBar.fire();
this.onRequestSyncScrollBar.fire();
return true;
}
@@ -3220,7 +3210,7 @@ export class InputHandler extends Disposable implements IInputHandler {
const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop;
this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1);
this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData()));
this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
} else {
this._activeBuffer.y--;
this._restrictCursor(); // quickfix to not run out of bounds
@@ -3235,7 +3225,7 @@ export class InputHandler extends Disposable implements IInputHandler {
*/
public fullReset(): boolean {
this._parser.reset();
this._onRequestReset.fire();
this.onRequestReset.fire();
return true;
}
@@ -3293,7 +3283,7 @@ export class InputHandler extends Disposable implements IInputHandler {
line.isWrapped = false;
}
}
this._dirtyRowService.markAllDirty();
this._dirtyRowTracker.markAllDirty();
this._setCursor(0, 0);
return true;
}
@@ -3344,4 +3334,60 @@ export class InputHandler extends Disposable implements IInputHandler {
if (data === ' q') return f(`P1$r${STYLES[opts.cursorStyle] - (opts.cursorBlink ? 1 : 0)} q`);
return f(`P0$r`);
}
public markRangeDirty(y1: number, y2: number): void {
this._dirtyRowTracker.markRangeDirty(y1, y2);
}
}
export interface IDirtyRowTracker {
readonly start: number;
readonly end: number;
clearRange(): void;
markDirty(y: number): void;
markRangeDirty(y1: number, y2: number): void;
markAllDirty(): void;
}
class DirtyRowTracker implements IDirtyRowTracker {
public start!: number;
public end!: number;
constructor(
@IBufferService private readonly _bufferService: IBufferService
) {
this.clearRange();
}
public clearRange(): void {
this.start = this._bufferService.buffer.y;
this.end = this._bufferService.buffer.y;
}
public markDirty(y: number): void {
if (y < this.start) {
this.start = y;
} else if (y > this.end) {
this.end = y;
}
}
public markRangeDirty(y1: number, y2: number): void {
if (y1 > y2) {
$temp = y1;
y1 = y2;
y2 = $temp;
}
if (y1 < this.start) {
this.start = y1;
}
if (y2 > this.end) {
this.end = y2;
}
}
public markAllDirty(): void {
this.markRangeDirty(0, this._bufferService.rows - 1);
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ interface INavigator {
// we want this module to live in common.
declare const navigator: INavigator;
const isNode = (typeof navigator === 'undefined') ? true : false;
export const isNode = (typeof navigator === 'undefined') ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
+147
View File
@@ -0,0 +1,147 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { isNode } from 'common/Platform';
interface ITaskQueue {
/**
* Adds a task to the queue which will run in a future idle callback.
*/
enqueue(task: () => void): void;
/**
* Flushes the queue, running all remaining tasks synchronously.
*/
flush(): void;
/**
* Clears any remaining tasks from the queue, these will not be run.
*/
clear(): void;
}
interface ITaskDeadline {
timeRemaining(): number;
}
type CallbackWithDeadline = (deadline: ITaskDeadline) => void;
abstract class TaskQueue implements ITaskQueue {
private _tasks: (() => void)[] = [];
private _idleCallback?: number;
private _i = 0;
protected abstract _requestCallback(callback: CallbackWithDeadline): number;
protected abstract _cancelCallback(identifier: number): void;
public enqueue(task: () => void): void {
this._tasks.push(task);
this._start();
}
public flush(): void {
while (this._i < this._tasks.length) {
this._tasks[this._i++]();
}
this.clear();
}
public clear(): void {
if (this._idleCallback) {
this._cancelCallback(this._idleCallback);
this._idleCallback = undefined;
}
this._i = 0;
this._tasks.length = 0;
}
private _start(): void {
if (!this._idleCallback) {
this._idleCallback = this._requestCallback(this._process.bind(this));
}
}
private _process(deadline: ITaskDeadline): void {
this._idleCallback = undefined;
let taskDuration = 0;
let longestTask = 0;
while (this._i < this._tasks.length) {
taskDuration = performance.now();
this._tasks[this._i++]();
taskDuration = performance.now() - taskDuration;
longestTask = Math.max(taskDuration, longestTask);
// Guess the following task will take a similar time to the longest task in this batch, allow
// additional room to try avoid exceeding the deadline
if (longestTask * 1.5 > deadline.timeRemaining()) {
this._start();
return;
}
}
this.clear();
}
}
/**
* A queue of that runs tasks over several tasks via setTimeout, trying to maintain above 60 frames
* per second. The tasks will run in the order they are enqueued, but they will run some time later,
* and care should be taken to ensure they're non-urgent and will not introduce race conditions.
*/
export class PriorityTaskQueue extends TaskQueue {
protected _requestCallback(callback: CallbackWithDeadline): number {
return setTimeout(() => callback(this._createDeadline(16)));
}
protected _cancelCallback(identifier: number): void {
clearTimeout(identifier);
}
private _createDeadline(duration: number): ITaskDeadline {
const end = performance.now() + duration;
return {
timeRemaining: () => Math.max(0, end - performance.now())
};
}
}
class IdleTaskQueueInternal extends TaskQueue {
protected _requestCallback(callback: IdleRequestCallback): number {
return requestIdleCallback(callback);
}
protected _cancelCallback(identifier: number): void {
cancelIdleCallback(identifier);
}
}
/**
* A queue of that runs tasks over several idle callbacks, trying to respect the idle callback's
* deadline given by the environment. The tasks will run in the order they are enqueued, but they
* will run some time later, and care should be taken to ensure they're non-urgent and will not
* introduce race conditions.
*
* This reverts to a {@link PriorityTaskQueue} if the environment does not support idle callbacks.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const IdleTaskQueue = (!isNode && 'requestIdleCallback' in window) ? IdleTaskQueueInternal : PriorityTaskQueue;
/**
* An object that tracks a single debounced task that will run on the next idle frame. When called
* multiple times, only the last set task will run.
*/
export class DebouncedIdleTask {
private _queue: ITaskQueue;
constructor() {
this._queue = new IdleTaskQueue();
}
public set(task: () => void): void {
this._queue.clear();
this._queue.enqueue(task);
}
public flush(): void {
this._queue.flush();
}
}
+12 -22
View File
@@ -3,8 +3,8 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService } from 'common/services/Services';
import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
@@ -17,8 +17,8 @@ export class MockBufferService implements IBufferService {
public serviceBrand: any;
public get buffer(): IBuffer { return this.buffers.active; }
public buffers: IBufferSet = {} as any;
public onResize: IEvent<{ cols: number, rows: number }> = new EventEmitter<{ cols: number, rows: number }>().event;
public onScroll: IEvent<number> = new EventEmitter<number>().event;
public onResize: IEvent<{ cols: number, rows: number }> = initEvent<{ cols: number, rows: number }>();
public onScroll: IEvent<number> = initEvent<number>();
public isUserScrolling: boolean = false;
constructor(
public cols: number,
@@ -60,7 +60,7 @@ export class MockCoreMouseService implements ICoreMouseService {
public addProtocol(name: string): void { }
public reset(): void { }
public triggerMouseEvent(event: ICoreMouseEvent): boolean { return false; }
public onProtocolChange: IEvent<CoreMouseEventType> = new EventEmitter<CoreMouseEventType>().event;
public onProtocolChange: IEvent<CoreMouseEventType> = initEvent<CoreMouseEventType>();
public explainEvents(events: CoreMouseEventType): { [event: string]: boolean } {
throw new Error('Method not implemented.');
}
@@ -92,24 +92,14 @@ export class MockCoreService implements ICoreService {
sendFocus: false,
wraparound: true
};
public onData: IEvent<string> = new EventEmitter<string>().event;
public onUserInput: IEvent<void> = new EventEmitter<void>().event;
public onBinary: IEvent<string> = new EventEmitter<string>().event;
public onData: IEvent<string> = initEvent<string>();
public onUserInput: IEvent<void> = initEvent<void>();
public onBinary: IEvent<string> = initEvent<string>();
public reset(): void { }
public triggerDataEvent(data: string, wasUserInput?: boolean): void { }
public triggerBinaryEvent(data: string): void { }
}
export class MockDirtyRowService implements IDirtyRowService {
public serviceBrand: any;
public start: number = 0;
public end: number = 0;
public clearRange(): void { }
public markDirty(y: number): void { }
public markRangeDirty(y1: number, y2: number): void { }
public markAllDirty(): void { }
}
export class MockLogService implements ILogService {
public serviceBrand: any;
public logLevel = LogLevelEnum.DEBUG;
@@ -123,7 +113,7 @@ export class MockOptionsService implements IOptionsService {
public serviceBrand: any;
public readonly rawOptions: Required<ITerminalOptions> = clone(DEFAULT_OPTIONS);
public options: Required<ITerminalOptions> = this.rawOptions;
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
public onOptionChange: IEvent<string> = initEvent<string>();
constructor(testOptions?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
@@ -159,7 +149,7 @@ export class MockUnicodeService implements IUnicodeService {
}
public versions: string[] = [];
public activeVersion: string = '';
public onChange: IEvent<string> = new EventEmitter<string>().event;
public onChange: IEvent<string> = initEvent<string>();
public wcwidth = (codepoint: number): number => this._provider.wcwidth(codepoint);
public getStringCellWidth(s: string): number {
throw new Error('Method not implemented.');
@@ -169,8 +159,8 @@ export class MockUnicodeService implements IUnicodeService {
export class MockDecorationService implements IDecorationService {
public serviceBrand: any;
public get decorations(): IterableIterator<IInternalDecoration> { return [].values(); }
public onDecorationRegistered = new EventEmitter<IInternalDecoration>().event;
public onDecorationRemoved = new EventEmitter<IInternalDecoration>().event;
public onDecorationRegistered = initEvent<IInternalDecoration>();
public onDecorationRemoved = initEvent<IInternalDecoration>();
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; }
public reset(): void { }
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { }
+4 -7
View File
@@ -4,7 +4,7 @@
*/
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm';
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter';
import { IDeleteEvent, IInsertEvent } from 'common/CircularList';
import { IParams } from 'common/parser/Types';
import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
@@ -71,12 +71,9 @@ export interface ICircularList<T> {
maxLength: number;
isFull: boolean;
onDeleteEmitter: IEventEmitter<IDeleteEvent>;
onDelete: IEvent<IDeleteEvent>;
onInsertEmitter: IEventEmitter<IInsertEvent>;
onInsert: IEvent<IInsertEvent>;
onTrimEmitter: IEventEmitter<number>;
onTrim: IEvent<number>;
onDelete: IEventWithEmitter<IDeleteEvent>;
onInsert: IEventWithEmitter<IInsertEvent>;
onTrim: IEventWithEmitter<number>;
get(index: number): T | undefined;
set(index: number, value: T): void;
+3 -3
View File
@@ -1071,7 +1071,7 @@ describe('Buffer', () => {
buffer.fillViewportRows();
const marker = buffer.addMarker(buffer.lines.length - 1);
assert.equal(marker.line, buffer.lines.length - 1);
buffer.lines.onTrimEmitter.fire(1);
buffer.lines.onTrim.fire(1);
assert.equal(marker.line, buffer.lines.length - 2);
});
it('should dispose of a marker if it is trimmed off the buffer', () => {
@@ -1081,7 +1081,7 @@ describe('Buffer', () => {
const marker = buffer.addMarker(0);
assert.equal(marker.isDisposed, false);
assert.equal(buffer.markers.length, 1);
buffer.lines.onTrimEmitter.fire(1);
buffer.lines.onTrim.fire(1);
assert.equal(marker.isDisposed, true);
assert.equal(buffer.markers.length, 0);
});
@@ -1094,7 +1094,7 @@ describe('Buffer', () => {
marker.onDispose(() => eventStack.push('disposed'));
assert.equal(marker.isDisposed, false);
assert.equal(buffer.markers.length, 1);
buffer.lines.onTrimEmitter.fire(1);
buffer.lines.onTrim.fire(1);
assert.equal(marker.isDisposed, true);
assert.equal(buffer.markers.length, 0);
assert.deepEqual(eventStack, ['disposed']);
+2 -2
View File
@@ -463,12 +463,12 @@ export class Buffer implements IBuffer {
let insertCountEmitted = 0;
for (let i = insertEvents.length - 1; i >= 0; i--) {
insertEvents[i].index += insertCountEmitted;
this.lines.onInsertEmitter.fire(insertEvents[i]);
this.lines.onInsert.fire(insertEvents[i]);
insertCountEmitted += insertEvents[i].amount;
}
const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);
if (amountToTrim > 0) {
this.lines.onTrimEmitter.fire(amountToTrim);
this.lines.onTrim.fire(amountToTrim);
}
}
}
+6 -8
View File
@@ -37,10 +37,8 @@ const enum Cell {
export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
/** Work variables to avoid garbage collection. */
const w: { startIndex: number } = {
startIndex: 0
};
// Work variables to avoid garbage collection
let $startIndex = 0;
/**
* Typed array based bufferline implementation.
@@ -178,10 +176,10 @@ export class BufferLine implements IBufferLine {
* to GC as it significantly reduced the amount of new objects/references needed.
*/
public loadCell(index: number, cell: ICellData): ICellData {
w.startIndex = index * CELL_SIZE;
cell.content = this._data[w.startIndex + Cell.CONTENT];
cell.fg = this._data[w.startIndex + Cell.FG];
cell.bg = this._data[w.startIndex + Cell.BG];
$startIndex = index * CELL_SIZE;
cell.content = this._data[$startIndex + Cell.CONTENT];
cell.fg = this._data[$startIndex + Cell.FG];
cell.bg = this._data[$startIndex + Cell.BG];
if (cell.content & Content.IS_COMBINED_MASK) {
cell.combinedData = this._combined[index];
}
+1 -1
View File
@@ -118,7 +118,7 @@ export function reflowLargerCreateNewLayout(lines: CircularList<IBufferLine>, to
const countToRemove = toRemove[++nextToRemoveIndex];
// Tell markers that there was a deletion
lines.onDeleteEmitter.fire({
lines.onDelete.fire({
index: i - countRemovedSoFar,
amount: countToRemove
});
+5 -6
View File
@@ -6,7 +6,7 @@
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IAttributeData } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { IOptionsService, IBufferService } from 'common/services/Services';
import { Disposable } from 'common/Lifecycle';
@@ -19,8 +19,7 @@ export class BufferSet extends Disposable implements IBufferSet {
private _alt!: Buffer;
private _activeBuffer!: Buffer;
private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
public readonly onBufferActivate = this.register(initEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
/**
* Create a new BufferSet for the given terminal.
@@ -42,7 +41,7 @@ export class BufferSet extends Disposable implements IBufferSet {
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
this._alt = new Buffer(false, this._optionsService, this._bufferService);
this._activeBuffer = this._normal;
this._onBufferActivate.fire({
this.onBufferActivate.fire({
activeBuffer: this._normal,
inactiveBuffer: this._alt
});
@@ -86,7 +85,7 @@ export class BufferSet extends Disposable implements IBufferSet {
this._alt.clearAllMarkers();
this._alt.clear();
this._activeBuffer = this._normal;
this._onBufferActivate.fire({
this.onBufferActivate.fire({
activeBuffer: this._normal,
inactiveBuffer: this._alt
});
@@ -105,7 +104,7 @@ export class BufferSet extends Disposable implements IBufferSet {
this._alt.x = this._normal.x;
this._alt.y = this._normal.y;
this._activeBuffer = this._alt;
this._onBufferActivate.fire({
this.onBufferActivate.fire({
activeBuffer: this._alt,
inactiveBuffer: this._normal
});
+3 -4
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { initEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IMarker } from 'common/Types';
@@ -15,8 +15,7 @@ export class Marker extends Disposable implements IMarker {
public get id(): number { return this._id; }
private _onDispose = new EventEmitter<void>();
public get onDispose(): IEvent<void> { return this._onDispose.event; }
public readonly onDispose = initEvent<void>();
constructor(
public line: number
@@ -31,7 +30,7 @@ export class Marker extends Disposable implements IMarker {
this.isDisposed = true;
this.line = -1;
// Emit before super.dispose such that dispose listeners get a change to react
this._onDispose.fire();
this.onDispose.fire();
super.dispose();
}
}
+23 -11
View File
@@ -4,7 +4,7 @@
* @license MIT
*/
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter';
declare const setTimeout: (handler: () => void, timeout?: number) => void;
@@ -33,12 +33,6 @@ const WRITE_TIMEOUT_MS = 12;
*/
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
// queueMicrotask polyfill for nodejs < v11
const qmt: (cb: () => void) => void = (typeof queueMicrotask === 'undefined')
? (cb: () => void) => { Promise.resolve().then(cb); }
: queueMicrotask;
export class WriteBuffer {
private _writeBuffer: (string | Uint8Array)[] = [];
private _callbacks: ((() => void) | undefined)[] = [];
@@ -46,11 +40,16 @@ export class WriteBuffer {
private _bufferOffset = 0;
private _isSyncWriting = false;
private _syncCalls = 0;
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
private _onWriteParsed = new EventEmitter<void>();
private _didUserInput = false;
public readonly onWriteParsed = initEvent<void>();
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
public handleUserInput(): void {
this._didUserInput = true;
}
/**
* @deprecated Unreliable, to be removed soon.
*/
@@ -105,6 +104,19 @@ export class WriteBuffer {
// schedule chunk processing for next event loop run
if (!this._writeBuffer.length) {
this._bufferOffset = 0;
// If this is the first write call after the user has done some input,
// parse it immediately to minimize input latency,
// otherwise schedule for the next event
if (this._didUserInput) {
this._didUserInput = false;
this._pendingData += data.length;
this._writeBuffer.push(data);
this._callbacks.push(callback);
this._innerWrite();
return;
}
setTimeout(() => this._innerWrite());
}
@@ -194,7 +206,7 @@ export class WriteBuffer {
// 2. spawn a promise immediately resolving to `true`
// (executed on the same queue, thus properly aligned before continuation happens)
result.catch(err => {
qmt(() => {throw err;});
queueMicrotask(() => {throw err;});
return Promise.resolve(false);
}).then(continuation);
return;
@@ -224,6 +236,6 @@ export class WriteBuffer {
this._pendingData = 0;
this._bufferOffset = 0;
}
this._onWriteParsed.fire();
this.onWriteParsed.fire();
}
}
+4 -4
View File
@@ -5,19 +5,19 @@
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm';
import { BufferApiView } from 'common/public/BufferApiView';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter';
import { ICoreTerminal } from 'common/Types';
export class BufferNamespaceApi implements IBufferNamespaceApi {
private _normal: BufferApiView;
private _alternate: BufferApiView;
private _onBufferChange = new EventEmitter<IBufferApi>();
public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }
public readonly onBufferChange = initEvent<IBufferApi>();
constructor(private _core: ICoreTerminal) {
this._normal = new BufferApiView(this._core.buffers.normal, 'normal');
this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');
this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
this._core.buffers.onBufferActivate(() => this.onBufferChange.fire(this.active));
}
public get active(): IBufferApi {
if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }
+6 -8
View File
@@ -6,7 +6,7 @@
import { IBufferService, IOptionsService } from 'common/services/Services';
import { BufferSet } from 'common/buffer/BufferSet';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types';
@@ -22,10 +22,8 @@ export class BufferService extends Disposable implements IBufferService {
/** Whether the user is scrolling (locks the scroll position) */
public isUserScrolling: boolean = false;
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
private _onScroll = new EventEmitter<number>();
public get onScroll(): IEvent<number> { return this._onScroll.event; }
public readonly onResize = initEvent<{ cols: number, rows: number }>();
public readonly onScroll = initEvent<number>();
public get buffer(): IBuffer { return this.buffers.active; }
@@ -49,7 +47,7 @@ export class BufferService extends Disposable implements IBufferService {
this.rows = rows;
this.buffers.resize(cols, rows);
this.buffers.setupTabStops(this.cols);
this._onResize.fire({ cols, rows });
this.onResize.fire({ cols, rows });
}
public reset(): void {
@@ -118,7 +116,7 @@ export class BufferService extends Disposable implements IBufferService {
buffer.ydisp = buffer.ybase;
}
this._onScroll.fire(buffer.ydisp);
this.onScroll.fire(buffer.ydisp);
}
/**
@@ -148,7 +146,7 @@ export class BufferService extends Disposable implements IBufferService {
}
if (!suppressScrollEvent) {
this._onScroll.fire(buffer.ydisp);
this.onScroll.fire(buffer.ydisp);
}
}
+4 -10
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
/**
@@ -170,9 +170,10 @@ export class CoreMouseService implements ICoreMouseService {
private _encodings: { [name: string]: CoreMouseEncoding } = {};
private _activeProtocol: string = '';
private _activeEncoding: string = '';
private _onProtocolChange = new EventEmitter<CoreMouseEventType>();
private _lastEvent: ICoreMouseEvent | null = null;
public readonly onProtocolChange = initEvent<CoreMouseEventType>();
constructor(
@IBufferService private readonly _bufferService: IBufferService,
@ICoreService private readonly _coreService: ICoreService
@@ -205,7 +206,7 @@ export class CoreMouseService implements ICoreMouseService {
throw new Error(`unknown protocol "${name}"`);
}
this._activeProtocol = name;
this._onProtocolChange.fire(this._protocols[name].events);
this.onProtocolChange.fire(this._protocols[name].events);
}
public get activeEncoding(): string {
@@ -225,13 +226,6 @@ export class CoreMouseService implements ICoreMouseService {
this._lastEvent = null;
}
/**
* Event to announce changes in mouse tracking.
*/
public get onProtocolChange(): IEvent<CoreMouseEventType> {
return this._onProtocolChange.event;
}
/**
* Triggers a mouse event to be sent.
*
+7 -10
View File
@@ -4,7 +4,7 @@
*/
import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { IDecPrivateModes, IModes } from 'common/Types';
import { clone } from 'common/Clone';
import { Disposable } from 'common/Lifecycle';
@@ -34,12 +34,9 @@ export class CoreService extends Disposable implements ICoreService {
// Circular dependency, this must be unset or memory will leak after Terminal.dispose
private _scrollToBottom: (() => void) | undefined;
private _onData = this.register(new EventEmitter<string>());
public get onData(): IEvent<string> { return this._onData.event; }
private _onUserInput = this.register(new EventEmitter<void>());
public get onUserInput(): IEvent<void> { return this._onUserInput.event; }
private _onBinary = this.register(new EventEmitter<string>());
public get onBinary(): IEvent<string> { return this._onBinary.event; }
public readonly onData = this.register(initEvent<string>());
public readonly onUserInput = this.register(initEvent<void>());
public readonly onBinary = this.register(initEvent<string>());
constructor(
// TODO: Move this into a service
@@ -74,12 +71,12 @@ export class CoreService extends Disposable implements ICoreService {
// Fire onUserInput so listeners can react as well (eg. clear selection)
if (wasUserInput) {
this._onUserInput.fire();
this.onUserInput.fire();
}
// Fire onData API
this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onData.fire(data);
this.onData.fire(data);
}
public triggerBinaryEvent(data: string): void {
@@ -87,6 +84,6 @@ export class CoreService extends Disposable implements ICoreService {
return;
}
this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onBinary.fire(data);
this.onBinary.fire(data);
}
}
+15 -21
View File
@@ -4,18 +4,16 @@
*/
import { css } from 'common/Color';
import { EventEmitter } from 'common/EventEmitter';
import { EventEmitter, initEvent } from 'common/EventEmitter';
import { Disposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm';
/** Work variables to avoid garbage collection. */
const w = {
xmin: 0,
xmax: 0
};
// Work variables to avoid garbage collection
let $xmin = 0;
let $xmax = 0;
export class DecorationService extends Disposable implements IDecorationService {
public serviceBrand: any;
@@ -27,10 +25,8 @@ export class DecorationService extends Disposable implements IDecorationService
*/
private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e?.marker.line);
private _onDecorationRegistered = this.register(new EventEmitter<IInternalDecoration>());
public get onDecorationRegistered(): IEvent<IInternalDecoration> { return this._onDecorationRegistered.event; }
private _onDecorationRemoved = this.register(new EventEmitter<IInternalDecoration>());
public get onDecorationRemoved(): IEvent<IInternalDecoration> { return this._onDecorationRemoved.event; }
public readonly onDecorationRegistered = this.register(initEvent<IInternalDecoration>());
public readonly onDecorationRemoved = this.register(initEvent<IInternalDecoration>());
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
@@ -44,13 +40,13 @@ export class DecorationService extends Disposable implements IDecorationService
decoration.onDispose(() => {
if (decoration) {
if (this._decorations.delete(decoration)) {
this._onDecorationRemoved.fire(decoration);
this.onDecorationRemoved.fire(decoration);
}
markerDispose.dispose();
}
});
this._decorations.insert(decoration);
this._onDecorationRegistered.fire(decoration);
this.onDecorationRegistered.fire(decoration);
}
return decoration;
}
@@ -76,9 +72,9 @@ export class DecorationService extends Disposable implements IDecorationService
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void {
this._decorations.forEachByKey(line, d => {
w.xmin = d.options.x ?? 0;
w.xmax = w.xmin + (d.options.width ?? 1);
if (x >= w.xmin && x < w.xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
$xmin = d.options.x ?? 0;
$xmax = $xmin + (d.options.width ?? 1);
if (x >= $xmin && x < $xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
callback(d);
}
});
@@ -86,7 +82,7 @@ export class DecorationService extends Disposable implements IDecorationService
public dispose(): void {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
this.onDecorationRemoved.fire(d);
}
this.reset();
}
@@ -97,10 +93,8 @@ class Decoration extends Disposable implements IInternalDecoration {
public element: HTMLElement | undefined;
public isDisposed: boolean = false;
public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>());
public readonly onRender = this.onRenderEmitter.event;
private _onDispose = this.register(new EventEmitter<void>());
public readonly onDispose = this._onDispose.event;
public readonly onRender = this.register(initEvent<HTMLElement>());
public readonly onDispose = this.register(initEvent<void>());
private _cachedBg: IColor | undefined | null = null;
public get backgroundColorRGB(): IColor | undefined {
@@ -141,7 +135,7 @@ class Decoration extends Disposable implements IInternalDecoration {
return;
}
this._isDisposed = true;
this._onDispose.fire();
this.onDispose.fire();
super.dispose();
}
}
-53
View File
@@ -1,53 +0,0 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IBufferService, IDirtyRowService } from 'common/services/Services';
export class DirtyRowService implements IDirtyRowService {
public serviceBrand: any;
private _start!: number;
private _end!: number;
public get start(): number { return this._start; }
public get end(): number { return this._end; }
constructor(
@IBufferService private readonly _bufferService: IBufferService
) {
this.clearRange();
}
public clearRange(): void {
this._start = this._bufferService.buffer.y;
this._end = this._bufferService.buffer.y;
}
public markDirty(y: number): void {
if (y < this._start) {
this._start = y;
} else if (y > this._end) {
this._end = y;
}
}
public markRangeDirty(y1: number, y2: number): void {
if (y1 > y2) {
const temp = y1;
y1 = y2;
y2 = temp;
}
if (y1 < this._start) {
this._start = y1;
}
if (y2 > this._end) {
this._end = y2;
}
}
public markAllDirty(): void {
this.markRangeDirty(0, this._bufferService.rows - 1);
}
}
+3 -4
View File
@@ -4,9 +4,9 @@
*/
import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { isMac } from 'common/Platform';
import { CursorStyle } from 'common/Types';
import { initEvent } from 'common/EventEmitter';
export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
cols: 80,
@@ -57,8 +57,7 @@ export class OptionsService implements IOptionsService {
public readonly rawOptions: Required<ITerminalOptions>;
public options: Required<ITerminalOptions>;
private _onOptionChange = new EventEmitter<string>();
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
public readonly onOptionChange = initEvent<string>();
constructor(options: Partial<ITerminalOptions>) {
// set the default value of each option
@@ -97,7 +96,7 @@ export class OptionsService implements IOptionsService {
// Don't fire an option change event if they didn't change
if (this.rawOptions[propName] !== value) {
this.rawOptions[propName] = value;
this._onOptionChange.fire(propName);
this.onOptionChange.fire(propName);
}
};
+2 -15
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IEvent, IEventEmitter, IEventWithEmitter } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, IOscLinkData } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
@@ -122,19 +122,6 @@ export interface ICharsetService {
setgCharset(g: number, charset: ICharset | undefined): void;
}
export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
export interface IDirtyRowService {
serviceBrand: undefined;
readonly start: number;
readonly end: number;
clearRange(): void;
markDirty(y: number): void;
markRangeDirty(y1: number, y2: number): void;
markAllDirty(): void;
}
export interface IServiceIdentifier<T> {
(...args: any[]): void;
type: T;
@@ -331,5 +318,5 @@ export interface IInternalDecoration extends IDecoration {
readonly options: IDecorationOptions;
readonly backgroundColorRGB: IColor | undefined;
readonly foregroundColorRGB: IColor | undefined;
readonly onRenderEmitter: IEventEmitter<HTMLElement>;
readonly onRender: IEventWithEmitter<HTMLElement>;
}
+4 -4
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
import { UnicodeV6 } from 'common/input/UnicodeV6';
@@ -13,8 +13,8 @@ export class UnicodeService implements IUnicodeService {
private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null);
private _active: string = '';
private _activeProvider: IUnicodeVersionProvider;
private _onChange = new EventEmitter<string>();
public get onChange(): IEvent<string> { return this._onChange.event; }
public readonly onChange = initEvent<string>();
constructor() {
const defaultProvider = new UnicodeV6();
@@ -37,7 +37,7 @@ export class UnicodeService implements IUnicodeService {
}
this._active = version;
this._activeProvider = this._providers[version];
this._onChange.fire(version);
this.onChange.fire(version);
}
public register(provider: IUnicodeVersionProvider): void {
+11 -17
View File
@@ -24,7 +24,7 @@
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { IBuffer } from 'common/buffer/Types';
import { CoreTerminal } from 'common/CoreTerminal';
import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter';
import { EventEmitter, forwardEvent, IEvent, initEvent } from 'common/EventEmitter';
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types';
@@ -32,17 +32,11 @@ export class Terminal extends CoreTerminal {
// TODO: We should remove options once components adopt optionsService
public get options(): Required<IInitializedTerminalOptions> { return this.optionsService.options; }
private _onBell = new EventEmitter<void>();
public get onBell(): IEvent<void> { return this._onBell.event; }
private _onCursorMove = new EventEmitter<void>();
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onTitleChange = new EventEmitter<string>();
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
private _onA11yCharEmitter = new EventEmitter<string>();
public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
private _onA11yTabEmitter = new EventEmitter<number>();
public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
public readonly onBell = initEvent<void>();
public readonly onCursorMove = initEvent<void>();
public readonly onTitleChange = initEvent<string>();
public readonly onA11yChar = initEvent<string>();
public readonly onA11yTab = initEvent<number>();
/**
* Creates a new `Terminal` object.
@@ -66,10 +60,10 @@ export class Terminal extends CoreTerminal {
// Setup InputHandler listeners
this.register(this._inputHandler.onRequestBell(() => this.bell()));
this.register(this._inputHandler.onRequestReset(() => this.reset()));
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove));
this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange));
this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar));
this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab));
}
public dispose(): void {
@@ -112,7 +106,7 @@ export class Terminal extends CoreTerminal {
}
public bell(): void {
this._onBell.fire();
this.onBell.fire();
}
/**