From 353a8c5388e3242af4957da5b89fc50a6e8ab663 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 10:21:31 -0700 Subject: [PATCH 01/35] Create CoreTerminal, fix createInstance in strict mode --- .vscode/settings.json | 3 +- .../src/WebglRenderer.api.ts | 2 +- src/Terminal.ts | 29 ++----- src/TestUtils.test.ts | 4 +- src/Types.d.ts | 10 +-- src/browser/TestUtils.test.ts | 6 +- src/browser/services/CharSizeService.ts | 2 +- src/browser/services/CoreBrowserService.ts | 2 +- src/browser/services/MouseService.ts | 2 +- src/browser/services/RenderService.ts | 2 +- src/browser/services/SelectionService.ts | 2 +- src/browser/services/Services.ts | 12 +-- src/browser/services/SoundService.ts | 2 +- src/browser/tsconfig.json | 5 +- src/common/CoreTerminal.ts | 56 ++++++++++++++ src/common/Types.d.ts | 9 +++ src/common/services/Services.ts | 75 ++++++------------- src/common/tsconfig.json | 5 +- src/tsconfig-base.json | 3 +- 19 files changed, 127 insertions(+), 104 deletions(-) create mode 100644 src/common/CoreTerminal.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f07ca545..38f4dd11 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "typescript.preferences.importModuleSpecifier": "non-relative", - "typescript.preferences.quoteStyle": "single" + "typescript.preferences.quoteStyle": "single", + "eslint.enable": true } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index daf7f6fc..7165edb3 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminalOptions } from '../../../src/Types'; +import { ITerminalOptions } from '../../../src/common/Types'; import { ITheme } from 'xterm'; import { assert } from 'chai'; import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; diff --git a/src/Terminal.ts b/src/Terminal.ts index e3a3801d..4a0bd82c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; +import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -39,39 +39,36 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, ICharsetService, IUnicodeService } from 'common/services/Services'; -import { OptionsService } from 'common/services/OptionsService'; +import { ICoreMouseService, ICoreService, IDirtyRowService, ICharsetService, IUnicodeService } from 'common/services/Services'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; -import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; -import { Disposable } from 'common/Lifecycle'; +import { MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { CoreService } from 'common/services/CoreService'; -import { LogService } from 'common/services/LogService'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { DirtyRowService } from 'common/services/DirtyRowService'; -import { InstantiationService } from 'common/services/InstantiationService'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { UnicodeService } from 'common/services/UnicodeService'; import { CharsetService } from 'common/services/CharsetService'; +import { CoreTerminal } from 'common/CoreTerminal'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; -export class Terminal extends Disposable implements ITerminal, IDisposable, IInputHandlingTerminal { +export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public screenElement: HTMLElement; @@ -92,14 +89,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _customKeyEventHandler: CustomKeyEventHandler; // common services - private _bufferService: IBufferService; private _coreService: ICoreService; private _charsetService: ICharsetService; private _coreMouseService: ICoreMouseService; private _dirtyRowService: IDirtyRowService; - private _instantiationService: IInstantiationService; - private _logService: ILogService; - public optionsService: IOptionsService; public unicodeService: IUnicodeService; // browser services @@ -192,16 +185,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp constructor( options: ITerminalOptions = {} ) { - super(); + super(options); + // TODO: Move these to CoreTerminal.ts // Setup and initialize common services - this._instantiationService = new InstantiationService(); - this.optionsService = new OptionsService(options); - this._instantiationService.setService(IOptionsService, this.optionsService); - this._bufferService = this._instantiationService.createInstance(BufferService); - this._instantiationService.setService(IBufferService, this._bufferService); - this._logService = this._instantiationService.createInstance(LogService); - this._instantiationService.setService(ILogService, this._logService); this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index cac0a43d..f0c56f15 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,9 +4,9 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types'; +import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm'; diff --git a/src/Types.d.ts b/src/Types.d.ts index cb0ba844..14494e89 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -4,7 +4,7 @@ */ import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; -import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types'; +import { ICharset, IAttributeData, CharData, CoreMouseEventType, ITerminalOptions } from 'common/Types'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; @@ -216,14 +216,6 @@ export interface ILinkifierAccessor { linkifier2: ILinkifier2; } -// TODO: The options that are not in the public API should be reviewed -export interface ITerminalOptions extends IPublicTerminalOptions { - [key: string]: any; - cancelEvents?: boolean; - convertEol?: boolean; - termName?: string; -} - export interface IBrowser { isNode: boolean; userAgent: string; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 96d1eae0..1869ab87 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -9,7 +9,7 @@ import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/re import { IColorSet } from 'browser/Types'; export class MockCharSizeService implements ICharSizeService { - public serviceBrand: any; + public serviceBrand: undefined; public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; } public onCharSizeChange: IEvent = new EventEmitter().event; constructor(public width: number, public height: number) {} @@ -17,7 +17,7 @@ export class MockCharSizeService implements ICharSizeService { } export class MockMouseService implements IMouseService { - public serviceBrand: any; + public serviceBrand: undefined; public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined { throw new Error('Not implemented'); } @@ -28,7 +28,7 @@ export class MockMouseService implements IMouseService { } export class MockRenderService implements IRenderService { - public serviceBrand: any; + public serviceBrand: undefined; public onDimensionsChange: IEvent = new EventEmitter().event; public onRenderedBufferChange: 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; diff --git a/src/browser/services/CharSizeService.ts b/src/browser/services/CharSizeService.ts index 7623109c..79696a28 100644 --- a/src/browser/services/CharSizeService.ts +++ b/src/browser/services/CharSizeService.ts @@ -8,7 +8,7 @@ import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharSizeService } from 'browser/services/Services'; export class CharSizeService implements ICharSizeService { - public serviceBrand: any; + public serviceBrand: undefined; public width: number = 0; public height: number = 0; diff --git a/src/browser/services/CoreBrowserService.ts b/src/browser/services/CoreBrowserService.ts index e3207d7b..985253d9 100644 --- a/src/browser/services/CoreBrowserService.ts +++ b/src/browser/services/CoreBrowserService.ts @@ -6,7 +6,7 @@ import { ICoreBrowserService } from './Services'; export class CoreBrowserService implements ICoreBrowserService { - public serviceBrand: any; + public serviceBrand: undefined; constructor( private _textarea: HTMLTextAreaElement diff --git a/src/browser/services/MouseService.ts b/src/browser/services/MouseService.ts index 39df6383..348ba64e 100644 --- a/src/browser/services/MouseService.ts +++ b/src/browser/services/MouseService.ts @@ -7,7 +7,7 @@ import { ICharSizeService, IRenderService, IMouseService } from './Services'; import { getCoords, getRawByteCoords } from 'browser/input/Mouse'; export class MouseService implements IMouseService { - public serviceBrand: any; + public serviceBrand: undefined; constructor( @IRenderService private readonly _renderService: IRenderService, diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index d7ff5814..61ba35ab 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -14,7 +14,7 @@ import { IOptionsService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; export class RenderService extends Disposable implements IRenderService { - public serviceBrand: any; + public serviceBrand: undefined; private _renderDebouncer: RenderDebouncer; private _screenDprMonitor: ScreenDprMonitor; diff --git a/src/browser/services/SelectionService.ts b/src/browser/services/SelectionService.ts index 43875f1b..d70cfa11 100644 --- a/src/browser/services/SelectionService.ts +++ b/src/browser/services/SelectionService.ts @@ -67,7 +67,7 @@ export const enum SelectionMode { * when the selection is ready to be redrawn (on an animation frame). */ export class SelectionService implements ISelectionService { - public serviceBrand: any; + public serviceBrand: undefined; protected _model: SelectionModel; diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 3754e6fe..28b83985 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -12,7 +12,7 @@ import { IDisposable } from 'common/Types'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { - serviceBrand: any; + serviceBrand: undefined; readonly width: number; readonly height: number; @@ -25,14 +25,14 @@ export interface ICharSizeService { export const ICoreBrowserService = createDecorator('CoreBrowserService'); export interface ICoreBrowserService { - serviceBrand: any; + serviceBrand: undefined; readonly isFocused: boolean; } export const IMouseService = createDecorator('MouseService'); export interface IMouseService { - serviceBrand: any; + serviceBrand: undefined; getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined; getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined; @@ -40,7 +40,7 @@ export interface IMouseService { export const IRenderService = createDecorator('RenderService'); export interface IRenderService extends IDisposable { - serviceBrand: any; + serviceBrand: undefined; onDimensionsChange: IEvent; /** @@ -72,7 +72,7 @@ export interface IRenderService extends IDisposable { export const ISelectionService = createDecorator('SelectionService'); export interface ISelectionService { - serviceBrand: any; + serviceBrand: undefined; readonly selectionText: string; readonly hasSelection: boolean; @@ -100,7 +100,7 @@ export interface ISelectionService { export const ISoundService = createDecorator('SoundService'); export interface ISoundService { - serviceBrand: any; + serviceBrand: undefined; playBellSound(): void; } diff --git a/src/browser/services/SoundService.ts b/src/browser/services/SoundService.ts index 4b54c999..8d940c13 100644 --- a/src/browser/services/SoundService.ts +++ b/src/browser/services/SoundService.ts @@ -7,7 +7,7 @@ import { IOptionsService } from 'common/services/Services'; import { ISoundService } from 'browser/services/Services'; export class SoundService implements ISoundService { - public serviceBrand: any; + public serviceBrand: undefined; private static _audioContext: AudioContext; diff --git a/src/browser/tsconfig.json b/src/browser/tsconfig.json index 7465bbd3..74b4c057 100644 --- a/src/browser/tsconfig.json +++ b/src/browser/tsconfig.json @@ -14,7 +14,10 @@ "common/*": [ "./common/*" ] } }, - "include": [ "./**/*" ], + "include": [ + "./**/*", + "../../typings/xterm.d.ts" + ], "references": [ { "path": "../common" } ] diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts new file mode 100644 index 00000000..6930ce07 --- /dev/null +++ b/src/common/CoreTerminal.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2014-2020 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * @license MIT + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + * + * Terminal Emulation References: + * http://vt100.net/ + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * http://invisible-island.net/vttest/ + * http://www.inwap.com/pdp10/ansicode.txt + * http://linux.die.net/man/4/console_codes + * http://linux.die.net/man/7/urxvt + */ + +import { Disposable } from 'common/Lifecycle'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService } from 'common/services/Services'; +import { InstantiationService } from 'common/services/InstantiationService'; +import { LogService } from 'common/services/LogService'; +import { BufferService } from 'common/services/BufferService'; +import { OptionsService } from 'common/services/OptionsService'; +import { ITerminalOptions } from './Types'; + +export class CoreTerminal extends Disposable { + protected readonly _instantiationService: IInstantiationService; + protected readonly _bufferService: IBufferService; + protected readonly _logService: ILogService; + + public readonly optionsService: IOptionsService; + + constructor( + options: ITerminalOptions + ) { + super(); + + // Setup and initialize services + this._instantiationService = new InstantiationService(); + this.optionsService = new OptionsService(options); + this._instantiationService.setService(IOptionsService, this.optionsService); + this._bufferService = this._instantiationService.createInstance(BufferService); + this._instantiationService.setService(IBufferService, this._bufferService); + this._logService = this._instantiationService.createInstance(LogService); + this._instantiationService.setService(ILogService, this._logService); + } + + public get cols(): number { return this._bufferService.cols; } + public get rows(): number { return this._bufferService.rows; } +} diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b250b45e..159c0876 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -3,6 +3,7 @@ * @license MIT */ +import { ITerminalOptions as IPublicTerminalOptions } from 'xterm'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; @@ -10,6 +11,14 @@ export interface IDisposable { dispose(): void; } +// TODO: The options that are not in the public API should be reviewed +export interface ITerminalOptions extends IPublicTerminalOptions { + [key: string]: any; + cancelEvents?: boolean; + convertEol?: boolean; + termName?: string; +} + export type XtermListener = (...args: any[]) => void; /** diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8a3e9b28..97a9f78f 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -10,7 +10,7 @@ import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); export interface IBufferService { - serviceBrand: any; + serviceBrand: undefined; readonly cols: number; readonly rows: number; @@ -51,12 +51,12 @@ export interface ICoreMouseService { /** * Human readable version of mouse events. */ - explainEvents(events: CoreMouseEventType): {[event: string]: boolean}; + explainEvents(events: CoreMouseEventType): { [event: string]: boolean }; } export const ICoreService = createDecorator('CoreService'); export interface ICoreService { - serviceBrand: any; + serviceBrand: undefined; /** * Initially the cursor will not be visible until the first time the terminal @@ -92,7 +92,7 @@ export interface ICoreService { export const ICharsetService = createDecorator('CharsetService'); export interface ICharsetService { - serviceBrand: any; + serviceBrand: undefined; charset: ICharset | undefined; readonly glevel: number; @@ -116,7 +116,7 @@ export interface ICharsetService { export const IDirtyRowService = createDecorator('DirtyRowService'); export interface IDirtyRowService { - serviceBrand: any; + serviceBrand: undefined; readonly start: number; readonly end: number; @@ -132,61 +132,32 @@ export interface IServiceIdentifier { type: T; } -export interface IConstructorSignature0 { - new(...services: { serviceBrand: any }[]): T; -} +export interface IBrandedService { + serviceBrand: undefined; +}; -export interface IConstructorSignature1 { - new(first: A1, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature2 { - new(first: A1, second: A2, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature3 { - new(first: A1, second: A2, third: A3, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature4 { - new(first: A1, second: A2, third: A3, fourth: A4, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature5 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature6 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature7 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, ...services: { serviceBrand: any }[]): T; -} - -export interface IConstructorSignature8 { - new(first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8, ...services: { serviceBrand: any }[]): T; -} +type GetLeadingNonServiceArgs = + Args extends [...IBrandedService[]] ? [] + : Args extends [infer A1, ...IBrandedService[]] ? [A1] + : Args extends [infer A1, infer A2, ...IBrandedService[]] ? [A1, A2] + : Args extends [infer A1, infer A2, infer A3, ...IBrandedService[]] ? [A1, A2, A3] + : Args extends [infer A1, infer A2, infer A3, infer A4, ...IBrandedService[]] ? [A1, A2, A3, A4] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, ...IBrandedService[]] ? [A1, A2, A3, A4, A5] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7] + : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, infer A8, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7, A8] + : never; export const IInstantiationService = createDecorator('InstantiationService'); export interface IInstantiationService { setService(id: IServiceIdentifier, instance: T): void; getService(id: IServiceIdentifier): T | undefined; - - createInstance(ctor: IConstructorSignature0): T; - createInstance(ctor: IConstructorSignature1, first: A1): T; - createInstance(ctor: IConstructorSignature2, first: A1, second: A2): T; - createInstance(ctor: IConstructorSignature3, first: A1, second: A2, third: A3): T; - createInstance(ctor: IConstructorSignature4, first: A1, second: A2, third: A3, fourth: A4): T; - createInstance(ctor: IConstructorSignature5, first: A1, second: A2, third: A3, fourth: A4, fifth: A5): T; - createInstance(ctor: IConstructorSignature6, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6): T; - createInstance(ctor: IConstructorSignature7, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7): T; - createInstance(ctor: IConstructorSignature8, first: A1, second: A2, third: A3, fourth: A4, fifth: A5, sixth: A6, seventh: A7, eigth: A8): T; + createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; } export const ILogService = createDecorator('LogService'); export interface ILogService { - serviceBrand: any; + serviceBrand: undefined; debug(message: any, ...optionalParams: any[]): void; info(message: any, ...optionalParams: any[]): void; @@ -196,7 +167,7 @@ export interface ILogService { export const IOptionsService = createDecorator('OptionsService'); export interface IOptionsService { - serviceBrand: any; + serviceBrand: undefined; readonly options: ITerminalOptions; @@ -311,7 +282,7 @@ export interface ITheme { export const IUnicodeService = createDecorator('UnicodeService'); export interface IUnicodeService { - serviceBrand: any; + serviceBrand: undefined; /** Register an Unicode version provider. */ register(provider: IUnicodeVersionProvider): void; /** Registered Unicode versions. */ diff --git a/src/common/tsconfig.json b/src/common/tsconfig.json index 59050a0b..7f35c80c 100644 --- a/src/common/tsconfig.json +++ b/src/common/tsconfig.json @@ -10,5 +10,8 @@ ], "baseUrl": ".." }, - "include": [ "./**/*" ] + "include": [ + "./**/*", + "../../typings/xterm.d.ts" + ] } diff --git a/src/tsconfig-base.json b/src/tsconfig-base.json index 84d0c924..0cd951a7 100644 --- a/src/tsconfig-base.json +++ b/src/tsconfig-base.json @@ -8,6 +8,7 @@ "removeComments": true, "pretty": true, - "incremental": true + "incremental": true, + "experimentalDecorators": true } } From 0d3bd2052b89a348898dbebc8efd27ec1b03718f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 10:26:29 -0700 Subject: [PATCH 02/35] Remove remaining common service inits to common --- src/Terminal.ts | 35 ----------------------------------- src/common/CoreTerminal.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 4a0bd82c..19fd2a88 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -46,22 +46,16 @@ import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; -import { ICoreMouseService, ICoreService, IDirtyRowService, ICharsetService, IUnicodeService } from 'common/services/Services'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; -import { CoreService } from 'common/services/CoreService'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; -import { DirtyRowService } from 'common/services/DirtyRowService'; -import { CoreMouseService } from 'common/services/CoreMouseService'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; -import { UnicodeService } from 'common/services/UnicodeService'; -import { CharsetService } from 'common/services/CharsetService'; import { CoreTerminal } from 'common/CoreTerminal'; // Let it work inside Node.js for automated testing purposes. @@ -88,13 +82,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT private _customKeyEventHandler: CustomKeyEventHandler; - // common services - private _coreService: ICoreService; - private _charsetService: ICharsetService; - private _coreMouseService: ICoreMouseService; - private _dirtyRowService: IDirtyRowService; - public unicodeService: IUnicodeService; - // browser services private _charSizeService: ICharSizeService; private _mouseService: IMouseService; @@ -137,15 +124,8 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; - public get cols(): number { return this._bufferService.cols; } - public get rows(): number { return this._bufferService.rows; } - private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } - private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } - private _onBinary = new EventEmitter(); - public get onBinary(): IEvent { return this._onBinary.event; } private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; } private _onLineFeed = new EventEmitter(); @@ -187,21 +167,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT ) { super(options); - // TODO: Move these to CoreTerminal.ts - // Setup and initialize common services - this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); - this._instantiationService.setService(ICoreService, this._coreService); - this._coreService.onData(e => this._onData.fire(e)); - this._coreService.onBinary(e => this._onBinary.fire(e)); - 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); - this._instantiationService.setService(ICharsetService, this._charsetService); - this._setupOptionsListeners(); this._setup(); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 6930ce07..dfffa1d4 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -22,20 +22,36 @@ */ import { Disposable } from 'common/Lifecycle'; -import { IInstantiationService, IOptionsService, IBufferService, ILogService } from 'common/services/Services'; +import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; import { ITerminalOptions } from './Types'; +import { CoreService } from 'common/services/CoreService'; +import { EventEmitter, IEvent } 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'; -export class CoreTerminal extends Disposable { +export abstract class CoreTerminal extends Disposable { protected readonly _instantiationService: IInstantiationService; protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; + protected readonly _coreService: ICoreService; + protected readonly _charsetService: ICharsetService; + protected readonly _coreMouseService: ICoreMouseService; + protected readonly _dirtyRowService: IDirtyRowService; + public readonly unicodeService: IUnicodeService; public readonly optionsService: IOptionsService; + private _onData = new EventEmitter(); + public get onData(): IEvent { return this._onData.event; } + private _onBinary = new EventEmitter(); + public get onBinary(): IEvent { return this._onBinary.event; } + constructor( options: ITerminalOptions ) { @@ -49,8 +65,22 @@ export class CoreTerminal extends Disposable { this._instantiationService.setService(IBufferService, this._bufferService); this._logService = this._instantiationService.createInstance(LogService); this._instantiationService.setService(ILogService, this._logService); + this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); + this._instantiationService.setService(ICoreService, this._coreService); + this._coreService.onData(e => this._onData.fire(e)); + this._coreService.onBinary(e => this._onBinary.fire(e)); + 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); + this._instantiationService.setService(ICharsetService, this._charsetService); } public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } + + public abstract scrollToBottom(): void; } From b80c14639e8b7ffd473f058302f9262be4c3194d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 10:34:37 -0700 Subject: [PATCH 03/35] Move option listeners to core --- src/Terminal.ts | 138 ++++++++++++++----------------------- src/common/CoreTerminal.ts | 59 ++++++++++++++-- 2 files changed, 104 insertions(+), 93 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 19fd2a88..6192beec 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -43,13 +43,12 @@ import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMo import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; -import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; import { MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; -import { IBufferSet, IBuffer } from 'common/buffer/Types'; +import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; @@ -119,7 +118,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT private _accessibilityManager: AccessibilityManager; private _colorManager: ColorManager; private _theme: ITheme; - private _windowsMode: IDisposable | undefined; // bufferline to clone/copy from for new blank lines private _blankLine: IBufferLine = null; @@ -128,8 +126,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT public get onCursorMove(): IEvent { 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 _onLineFeed = new EventEmitter(); - public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onRender = new EventEmitter<{ start: number, end: number }>(); public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } private _onResize = new EventEmitter<{ cols: number, rows: number }>(); @@ -167,7 +163,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT ) { super(options); - this._setupOptionsListeners(); this._setup(); this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); @@ -220,22 +215,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } } - private _enableWindowsMode(): void { - if (!this._windowsMode) { - const disposables: IDisposable[] = []; - disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); - disposables.push(this.addCsiHandler({ final: 'H' }, () => { - updateWindowsModeWrappedState(this._bufferService); - return false; - })); - this._windowsMode = { - dispose: () => { - disposables.forEach(d => d.dispose()); - } - }; - } - } - /** * Convenience property to active buffer. */ @@ -243,10 +222,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT return this.buffers.active; } - public get buffers(): IBufferSet { - return this._bufferService.buffers; - } - /** * Focus the terminal. Delegates focus handling to the terminal's DOM element. */ @@ -256,68 +231,59 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } } - private _setupOptionsListeners(): void { + protected _updateOptions(key: string): void { + super._updateOptions(key); + // TODO: These listeners should be owned by individual components - this.optionsService.onOptionChange(key => { - switch (key) { - case 'fontFamily': - case 'fontSize': - // When the font changes the size of the cells may change which requires a renderer clear - this._renderService?.clear(); - this._charSizeService?.measure(); - break; - case 'cursorBlink': - case 'cursorStyle': - // The DOM renderer needs a row refresh to update the cursor styles - this.refresh(this.buffer.y, this.buffer.y); - break; - case 'drawBoldTextInBrightColors': - case 'letterSpacing': - case 'lineHeight': - case 'fontWeight': - case 'fontWeightBold': - case 'minimumContrastRatio': - // When the font changes the size of the cells may change which requires a renderer clear - if (this._renderService) { - this._renderService.clear(); - this._renderService.onResize(this.cols, this.rows); - this.refresh(0, this.rows - 1); + switch (key) { + case 'fontFamily': + case 'fontSize': + // When the font changes the size of the cells may change which requires a renderer clear + this._renderService?.clear(); + this._charSizeService?.measure(); + break; + case 'cursorBlink': + case 'cursorStyle': + // The DOM renderer needs a row refresh to update the cursor styles + this.refresh(this.buffer.y, this.buffer.y); + break; + case 'drawBoldTextInBrightColors': + case 'letterSpacing': + case 'lineHeight': + case 'fontWeight': + case 'fontWeightBold': + case 'minimumContrastRatio': + // When the font changes the size of the cells may change which requires a renderer clear + if (this._renderService) { + this._renderService.clear(); + this._renderService.onResize(this.cols, this.rows); + this.refresh(0, this.rows - 1); + } + break; + case 'rendererType': + if (this._renderService) { + this._renderService.setRenderer(this._createRenderer()); + this._renderService.onResize(this.cols, this.rows); + } + break; + case 'scrollback': + this.viewport?.syncScrollArea(); + break; + case 'screenReaderMode': + if (this.optionsService.options.screenReaderMode) { + if (!this._accessibilityManager && this._renderService) { + this._accessibilityManager = new AccessibilityManager(this, this._renderService); } - break; - case 'rendererType': - if (this._renderService) { - this._renderService.setRenderer(this._createRenderer()); - this._renderService.onResize(this.cols, this.rows); - } - break; - case 'scrollback': - this.buffers.resize(this.cols, this.rows); - this.viewport?.syncScrollArea(); - break; - case 'screenReaderMode': - if (this.optionsService.options.screenReaderMode) { - if (!this._accessibilityManager && this._renderService) { - this._accessibilityManager = new AccessibilityManager(this, this._renderService); - } - } else { - this._accessibilityManager?.dispose(); - this._accessibilityManager = null; - } - break; - case 'tabStopWidth': this.buffers.setupTabStops(); break; - case 'theme': - this._setTheme(this.optionsService.options.theme); - break; - case 'windowsMode': - if (this.optionsService.options.windowsMode) { - this._enableWindowsMode(); - } else { - this._windowsMode?.dispose(); - this._windowsMode = undefined; - } - break; - } - }); + } else { + this._accessibilityManager?.dispose(); + this._accessibilityManager = null; + } + break; + case 'tabStopWidth': this.buffers.setupTabStops(); break; + case 'theme': + this._setTheme(this.optionsService.options.theme); + break; + } } /** diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index dfffa1d4..7a8f5bff 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -27,13 +27,16 @@ import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; import { BufferService } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; -import { ITerminalOptions } from './Types'; +import { ITerminalOptions, IDisposable } from './Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent } 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'; +import { IFunctionIdentifier, IParams } from 'common/parser/Types'; +import { IBufferSet } from 'common/buffer/Types'; export abstract class CoreTerminal extends Disposable { protected readonly _instantiationService: IInstantiationService; @@ -47,10 +50,18 @@ export abstract class CoreTerminal extends Disposable { public readonly unicodeService: IUnicodeService; public readonly optionsService: IOptionsService; - private _onData = new EventEmitter(); - public get onData(): IEvent { return this._onData.event; } + protected _windowsMode: IDisposable | undefined; + private _onBinary = new EventEmitter(); public get onBinary(): IEvent { return this._onBinary.event; } + private _onData = new EventEmitter(); + public get onData(): IEvent { return this._onData.event; } + protected _onLineFeed = new EventEmitter(); + public get onLineFeed(): IEvent { return this._onLineFeed.event; } + + public get cols(): number { return this._bufferService.cols; } + public get rows(): number { return this._bufferService.rows; } + public get buffers(): IBufferSet { return this._bufferService.buffers; } constructor( options: ITerminalOptions @@ -67,8 +78,6 @@ export abstract class CoreTerminal extends Disposable { this._instantiationService.setService(ILogService, this._logService); this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); - this._coreService.onData(e => this._onData.fire(e)); - this._coreService.onBinary(e => this._onBinary.fire(e)); this._coreMouseService = this._instantiationService.createInstance(CoreMouseService); this._instantiationService.setService(ICoreMouseService, this._coreMouseService); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); @@ -77,10 +86,46 @@ export abstract class CoreTerminal extends Disposable { this._instantiationService.setService(IUnicodeService, this.unicodeService); this._charsetService = this._instantiationService.createInstance(CharsetService); this._instantiationService.setService(ICharsetService, this._charsetService); + + // Setup listeners + this._coreService.onData(e => this._onData.fire(e)); + this._coreService.onBinary(e => this._onBinary.fire(e)); + this.optionsService.onOptionChange(key => this._updateOptions(key)); } - public get cols(): number { return this._bufferService.cols; } - public get rows(): number { return this._bufferService.rows; } + protected _updateOptions(key: string): void { + // TODO: These listeners should be owned by individual components + switch (key) { + case 'scrollback': + this.buffers.resize(this.cols, this.rows); + break; + case 'windowsMode': + if (this.optionsService.options.windowsMode) { + this._enableWindowsMode(); + } else { + this._windowsMode?.dispose(); + this._windowsMode = undefined; + } + break; + } + } + + protected _enableWindowsMode(): void { + if (!this._windowsMode) { + const disposables: IDisposable[] = []; + disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService))); + disposables.push(this.addCsiHandler({ final: 'H' }, () => { + updateWindowsModeWrappedState(this._bufferService); + return false; + })); + this._windowsMode = { + dispose: () => { + disposables.forEach(d => d.dispose()); + } + }; + } + } public abstract scrollToBottom(): void; + public abstract addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; } From 4cec11be9c28fc593780d8e0c7ee2188526d41b9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 11:11:05 -0700 Subject: [PATCH 04/35] Move bracketed paste mode to coreservice --- src/InputHandler.ts | 4 ++-- src/Terminal.ts | 16 +++++----------- src/Types.d.ts | 2 -- src/browser/Clipboard.ts | 8 ++++---- src/common/CoreTerminal.ts | 17 ++++++++++++++++- src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 1 + 8 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3717256e..371391da 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1802,7 +1802,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) - this._terminal.bracketedPasteMode = true; + this._coreService.decPrivateModes.bracketedPasteMode = true; break; } } @@ -2020,7 +2020,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) - this._terminal.bracketedPasteMode = false; + this._coreService.decPrivateModes.bracketedPasteMode = false; break; } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 6192beec..e97f3e58 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -90,7 +90,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // modes public insertMode: boolean; - public bracketedPasteMode: boolean; // mouse properties public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; @@ -173,20 +172,19 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT return; } super.dispose(); - this._windowsMode?.dispose(); - this._windowsMode = undefined; this._renderService?.dispose(); this._customKeyEventHandler = null; this.write = () => { }; this.element?.parentNode?.removeChild(this.element); } - private _setup(): void { + protected _setup(): void { + super._setup(); + this._customKeyEventHandler = null; // modes this.insertMode = false; - this.bracketedPasteMode = false; this._userScrolling = false; @@ -209,10 +207,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT if (!this.linkifier2) { this.linkifier2 = this._instantiationService.createInstance(Linkifier2); } - - if (this.options.windowsMode) { - this._enableWindowsMode(); - } } /** @@ -337,7 +331,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } copyHandler(event, this._selectionService); })); - const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea, this.bracketedPasteMode, this._coreService); + const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea, this._coreService); this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); @@ -982,7 +976,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } public paste(data: string): void { - paste(data, this.textarea, this.bracketedPasteMode, this._coreService); + paste(data, this.textarea, this._coreService); } /** diff --git a/src/Types.d.ts b/src/Types.d.ts index 14494e89..4aaf9fa8 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -22,7 +22,6 @@ export type LineData = CharData[]; */ export interface IInputHandlingTerminal { insertMode: boolean; - bracketedPasteMode: boolean; sendFocus: boolean; buffers: IBufferSet; @@ -135,7 +134,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc buffer: IBuffer; buffers: IBufferSet; viewport: IViewport; - bracketedPasteMode: boolean; optionsService: IOptionsService; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; diff --git a/src/browser/Clipboard.ts b/src/browser/Clipboard.ts index a7c48bfb..5fe1e8fc 100644 --- a/src/browser/Clipboard.ts +++ b/src/browser/Clipboard.ts @@ -42,17 +42,17 @@ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionServ * @param ev The original paste event to be handled * @param term The terminal on which to apply the handled paste event */ -export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void { +export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void { ev.stopPropagation(); if (ev.clipboardData) { const text = ev.clipboardData.getData('text/plain'); - paste(text, textarea, bracketedPasteMode, coreService); + paste(text, textarea, coreService); } } -export function paste(text: string, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void { +export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void { text = prepareTextForTerminal(text); - text = bracketTextForPaste(text, bracketedPasteMode); + text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode); coreService.triggerDataEvent(text, true); textarea.value = ''; } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 7a8f5bff..5428a3bc 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -50,7 +50,7 @@ export abstract class CoreTerminal extends Disposable { public readonly unicodeService: IUnicodeService; public readonly optionsService: IOptionsService; - protected _windowsMode: IDisposable | undefined; + private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); public get onBinary(): IEvent { return this._onBinary.event; } @@ -93,6 +93,21 @@ export abstract class CoreTerminal extends Disposable { this.optionsService.onOptionChange(key => this._updateOptions(key)); } + public dispose(): void { + if (this._isDisposed) { + return; + } + super.dispose(); + this._windowsMode?.dispose(); + this._windowsMode = undefined; + } + + protected _setup(): void { + if (this.optionsService.options.windowsMode) { + this._enableWindowsMode(); + } + } + protected _updateOptions(key: string): void { // TODO: These listeners should be owned by individual components switch (key) { diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index cfa1abd7..6d47f05f 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -61,6 +61,7 @@ export class MockCoreService implements ICoreService { public decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, applicationKeypad: false, + bracketedPasteMode: false, origin: false, wraparound: true }; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 159c0876..c94093db 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -163,6 +163,7 @@ export interface IMarker extends IDisposable { export interface IDecPrivateModes { applicationCursorKeys: boolean; applicationKeypad: boolean; + bracketedPasteMode: boolean; origin: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 2f0ff72f..56a0c04f 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -11,6 +11,7 @@ import { clone } from 'common/Clone'; const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, + bracketedPasteMode: false, origin: false, wraparound: true // defaults: xterm - true, vt100 - false }); From d5729f9216af511d11b183fb2a7eaa58bf2f616b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 11:26:20 -0700 Subject: [PATCH 05/35] Move insertMode into core service --- src/InputHandler.test.ts | 14 +++++++------- src/InputHandler.ts | 10 +++------- src/Terminal.test.ts | 6 +++--- src/Terminal.ts | 17 +++++------------ src/TestUtils.test.ts | 2 -- src/Types.d.ts | 1 - src/common/TestUtils.test.ts | 5 ++++- src/common/Types.d.ts | 3 +++ src/common/services/CoreService.ts | 9 ++++++++- src/common/services/Services.ts | 3 ++- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 84d4dae5..6c5b9cbb 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -102,16 +102,16 @@ describe('InputHandler', () => { }); }); describe('setMode', () => { - it('should toggle Terminal.bracketedPasteMode', () => { + it('should toggle bracketedPasteMode', () => { const terminal = new MockInputHandlingTerminal(); - terminal.bracketedPasteMode = false; - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const coreService = new MockCoreService(); + const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, true); + assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); // Reset bracketed paste mode inputHandler.resetModePrivate(Params.fromArray([2004])); - assert.equal(terminal.bracketedPasteMode, false); + assert.equal(coreService.decPrivateModes.bracketedPasteMode, false); }); }); describe('regression tests', function(): void { @@ -1453,9 +1453,9 @@ describe('InputHandler', () => { }); it('should reset IRM', () => { term.writeSync('\x1b[4h'); - assert.equal(term.insertMode, true); + assert.equal((term as any)._coreService.modes.insertMode, true); term.writeSync('\x1b[!p'); - assert.equal(term.insertMode, false); + assert.equal((term as any)._coreService.modes.insertMode, false); }); it('should reset cursor visibility', () => { term.writeSync('\x1b[?25l'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 371391da..c5c16501 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -489,7 +489,7 @@ export class InputHandler extends Disposable implements IInputHandler { const screenReaderMode = this._optionsService.options.screenReaderMode; const cols = this._bufferService.cols; const wraparoundMode = this._coreService.decPrivateModes.wraparound; - const insertMode = this._terminal.insertMode; + const insertMode = this._coreService.modes.insertMode; const curAttr = this._curAttrData; let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); @@ -1587,15 +1587,12 @@ export class InputHandler extends Disposable implements IInputHandler { * | 4 | Insert Mode (IRM). | #Y | * | 12 | Send/receive (SRM). Always off. | #N | * | 20 | Automatic Newline (LNM). Always off. | #N | - * - * - * FIXME: why is LNM commented out? */ public setMode(params: IParams): void { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: - this._terminal.insertMode = true; + this._coreService.modes.insertMode = true; break; case 20: // this._t.convertEol = true; @@ -1833,7 +1830,7 @@ export class InputHandler extends Disposable implements IInputHandler { for (let i = 0; i < params.length; i++) { switch (params.params[i]) { case 4: - this._terminal.insertMode = false; + this._coreService.modes.insertMode = false; break; case 20: // this._t.convertEol = false; @@ -2356,7 +2353,6 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; - this._terminal.insertMode = false; this._terminal.viewport?.syncScrollArea(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 4d590699..d70ccb45 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -991,7 +991,7 @@ describe('Terminal', () => { term.writeSync(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('abcde'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); @@ -1003,7 +1003,7 @@ describe('Terminal', () => { term.writeSync(Array(9).join('0123456789').slice(-80)); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('¥¥¥'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥'); @@ -1016,7 +1016,7 @@ describe('Terminal', () => { term.writeSync(Array(41).join('¥')); term.buffer.x = 10; term.buffer.y = 0; - term.insertMode = true; + term.write('\x1b[4h'); term.writeSync('a'); expect(term.buffer.lines.get(0).length).eql(term.cols); expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); diff --git a/src/Terminal.ts b/src/Terminal.ts index e97f3e58..5c2bbd7d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -88,9 +88,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT private _selectionService: ISelectionService; private _soundService: ISoundService; - // modes - public insertMode: boolean; - // mouse properties public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; public sendFocus: boolean; @@ -179,15 +176,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } protected _setup(): void { - super._setup(); - - this._customKeyEventHandler = null; - - // modes - this.insertMode = false; - - this._userScrolling = false; - if (this._inputHandler) { this._inputHandler.reset(); } else { @@ -201,6 +189,11 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this.register(this._inputHandler); } + super._setup(); + + this._customKeyEventHandler = null; + + this._userScrolling = false; if (!this.linkifier) { this.linkifier = this._instantiationService.createInstance(Linkifier); } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f0c56f15..ea0a5884 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -203,8 +203,6 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { public onA11yCharEmitter: EventEmitter; public onA11yTabEmitter: EventEmitter; - public insertMode: boolean; - public bracketedPasteMode: boolean; public sendFocus: boolean; public buffers: IBufferSet; public buffer: IBuffer = new MockBuffer(); diff --git a/src/Types.d.ts b/src/Types.d.ts index 4aaf9fa8..a2000551 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -21,7 +21,6 @@ export type LineData = CharData[]; * InputHandler cleanly from the ITerminal interface. */ export interface IInputHandlingTerminal { - insertMode: boolean; sendFocus: boolean; buffers: IBufferSet; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 6d47f05f..389c5382 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -9,7 +9,7 @@ import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { IBufferSet, IBuffer } from 'common/buffer/Types'; import { BufferSet } from 'common/buffer/BufferSet'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; export class MockBufferService implements IBufferService { @@ -58,6 +58,9 @@ export class MockCoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; public isFocused: boolean = false; + public modes: IModes = { + insertMode: false + }; public decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, applicationKeypad: false, diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index c94093db..1d616f54 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -159,6 +159,9 @@ export interface IMarker extends IDisposable { readonly isDisposed: boolean; readonly line: number; } +export interface IModes { + insertMode: boolean; +} export interface IDecPrivateModes { applicationCursorKeys: boolean; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 56a0c04f..285532b5 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -5,9 +5,13 @@ import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { IDecPrivateModes } from 'common/Types'; +import { IDecPrivateModes, IModes } from 'common/Types'; import { clone } from 'common/Clone'; +const DEFAULT_MODES: IModes = Object.freeze({ + insertMode: false +}); + const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationCursorKeys: false, applicationKeypad: false, @@ -21,6 +25,7 @@ export class CoreService implements ICoreService { public isCursorInitialized: boolean = false; public isCursorHidden: boolean = false; + public modes: IModes; public decPrivateModes: IDecPrivateModes; private _onData = new EventEmitter(); @@ -37,10 +42,12 @@ export class CoreService implements ICoreService { @ILogService private readonly _logService: ILogService, @IOptionsService private readonly _optionsService: IOptionsService ) { + this.modes = clone(DEFAULT_MODES); this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } public reset(): void { + this.modes = clone(DEFAULT_MODES); this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 97a9f78f..62ab45fd 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -5,7 +5,7 @@ import { IEvent } from 'common/EventEmitter'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions } from 'common/Types'; +import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes } from 'common/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; export const IBufferService = createDecorator('BufferService'); @@ -65,6 +65,7 @@ export interface ICoreService { isCursorInitialized: boolean; isCursorHidden: boolean; + readonly modes: IModes; readonly decPrivateModes: IDecPrivateModes; readonly onData: IEvent; From 37996bcd15be021500db0e4ad6f32233f0a8d6e8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 13:24:11 -0700 Subject: [PATCH 06/35] Move sendFocus to core service --- src/InputHandler.ts | 4 ++-- src/Terminal.ts | 5 ++--- src/TestUtils.test.ts | 1 - src/Types.d.ts | 2 -- src/common/TestUtils.test.ts | 1 + src/common/Types.d.ts | 1 + src/common/services/CoreService.ts | 1 + 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c5c16501..7732d4cf 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1771,7 +1771,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 1004: // send focusin/focusout events // focusin: ^[[I // focusout: ^[[O - this._terminal.sendFocus = true; + this._coreService.decPrivateModes.sendFocus = true; break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECSET 1005 not supported (see #2507)'); @@ -1986,7 +1986,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._coreMouseService.activeProtocol = 'NONE'; break; case 1004: // send focusin/focusout events - this._terminal.sendFocus = false; + this._coreService.decPrivateModes.sendFocus = false; break; case 1005: // utf8 ext mode mouse - removed in #2507 this._logService.debug('DECRST 1005 not supported (see #2507)'); diff --git a/src/Terminal.ts b/src/Terminal.ts index 5c2bbd7d..bb02671f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -90,7 +90,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // mouse properties public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; - public sendFocus: boolean; // write buffer private _writeBuffer: WriteBuffer; @@ -277,7 +276,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT * Binds the desired focus behavior on a given terminal object. */ private _onTextAreaFocus(ev: KeyboardEvent): void { - if (this.sendFocus) { + if (this._coreService.decPrivateModes.sendFocus) { this._coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); @@ -302,7 +301,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // screen readers reading it out. this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); - if (this.sendFocus) { + if (this._coreService.decPrivateModes.sendFocus) { this._coreService.triggerDataEvent(C0.ESC + '[O'); } this.element.classList.remove('focus'); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index ea0a5884..c15ce560 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -203,7 +203,6 @@ export class MockTerminal implements ITerminal { export class MockInputHandlingTerminal implements IInputHandlingTerminal { public onA11yCharEmitter: EventEmitter; public onA11yTabEmitter: EventEmitter; - public sendFocus: boolean; public buffers: IBufferSet; public buffer: IBuffer = new MockBuffer(); public viewport: IViewport; diff --git a/src/Types.d.ts b/src/Types.d.ts index a2000551..15eda648 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -21,8 +21,6 @@ export type LineData = CharData[]; * InputHandler cleanly from the ITerminal interface. */ export interface IInputHandlingTerminal { - sendFocus: boolean; - buffers: IBufferSet; buffer: IBuffer; viewport: IViewport; diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 389c5382..55df896e 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -66,6 +66,7 @@ export class MockCoreService implements ICoreService { applicationKeypad: false, bracketedPasteMode: false, origin: false, + sendFocus: false, wraparound: true }; public onData: IEvent = new EventEmitter().event; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 1d616f54..cc7928c3 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -168,6 +168,7 @@ export interface IDecPrivateModes { applicationKeypad: boolean; bracketedPasteMode: boolean; origin: boolean; + sendFocus: boolean; wraparound: boolean; // defaults: xterm - true, vt100 - false } diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 285532b5..55d425aa 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -17,6 +17,7 @@ const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({ applicationKeypad: false, bracketedPasteMode: false, origin: false, + sendFocus: false, wraparound: true // defaults: xterm - true, vt100 - false }); From c5ba1d62e7a6375938026ede64ff09d6e1ac2528 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 13:31:45 -0700 Subject: [PATCH 07/35] Remove mouseEvents from Terminal Depend on CoreMouseService directly --- src/Terminal.ts | 12 ++++-------- src/common/TestUtils.test.ts | 1 + src/common/services/CoreMouseService.ts | 4 ++++ src/common/services/Services.ts | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index bb02671f..c88a0715 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -88,9 +88,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT private _selectionService: ISelectionService; private _soundService: ISoundService; - // mouse properties - public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE; - // write buffer private _writeBuffer: WriteBuffer; @@ -500,7 +497,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e))); // apply mouse event classes set by escape codes before terminal was attached - if (this.mouseEvents) { + if (this._coreMouseService.areMouseEventsActive) { this._selectionService.disable(); this.element.classList.add('enable-mouse-events'); } else { @@ -675,7 +672,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT }; this._coreMouseService.onProtocolChange(events => { // apply global changes on events - this.mouseEvents = events; if (events) { if (this.optionsService.options.logLevel === 'debug') { this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events)); @@ -733,7 +729,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this.mouseEvents || this._selectionService.shouldForceSelection(ev)) { + if (!this._coreMouseService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) { return; } @@ -787,13 +783,13 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this._coreMouseService.areMouseEventsActive) return; this.viewport.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { - if (this.mouseEvents) return; + if (this._coreMouseService.areMouseEventsActive) return; if (!this.viewport.onTouchMove(ev)) { return this.cancel(ev); } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 55df896e..e5b3a425 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -31,6 +31,7 @@ export class MockBufferService implements IBufferService { } export class MockCoreMouseService implements ICoreMouseService { + public areMouseEventsActive: boolean = false; public activeEncoding: string = ''; public activeProtocol: string = ''; public addEncoding(name: string): void {} diff --git a/src/common/services/CoreMouseService.ts b/src/common/services/CoreMouseService.ts index c0ed3c6a..29a84761 100644 --- a/src/common/services/CoreMouseService.ts +++ b/src/common/services/CoreMouseService.ts @@ -192,6 +192,10 @@ export class CoreMouseService implements ICoreMouseService { return this._activeProtocol; } + public get areMouseEventsActive(): boolean { + return this._protocols[this._activeProtocol].events !== 0; + } + public set activeProtocol(name: string) { if (!this._protocols[name]) { throw new Error(`unknown protocol "${name}"`); diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 62ab45fd..05a6f3ae 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -27,6 +27,7 @@ export const ICoreMouseService = createDecorator('CoreMouseSe export interface ICoreMouseService { activeProtocol: string; activeEncoding: string; + areMouseEventsActive: boolean; addProtocol(name: string, protocol: ICoreMouseProtocol): void; addEncoding(name: string, encoding: CoreMouseEncoding): void; reset(): void; From 9346eba2f2916d4bad8e221dca7b85508915e160 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 16:18:43 -0700 Subject: [PATCH 08/35] Remove Terminal.showCursor dep from input handler --- src/InputHandler.ts | 4 ++-- src/Terminal.test.ts | 2 -- src/Terminal.ts | 8 ++++---- src/TestUtils.test.ts | 6 ------ src/Types.d.ts | 4 ---- src/browser/services/RenderService.ts | 6 ++++-- 6 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 7732d4cf..c3f3446e 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1794,9 +1794,9 @@ export class InputHandler extends Disposable implements IInputHandler { case 47: // alt screen buffer case 1047: // alt screen buffer this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); + this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); this._terminal.viewport?.syncScrollArea(); - this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -2012,9 +2012,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (params.params[i] === 1049) { this.restoreCursor(); } + this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); this._terminal.viewport?.syncScrollArea(); - this._terminal.showCursor(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index d70ccb45..40e1b141 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -140,7 +140,6 @@ describe('Terminal', () => { }; beforeEach(() => { - term.showCursor = () => { }; term.clearSelection = () => { }; }); @@ -531,7 +530,6 @@ describe('Terminal', () => { let evKeyPress: any; beforeEach(() => { - term.showCursor = () => { }; term.clearSelection = () => { }; // term.compositionHelper = { // isComposing: false, diff --git a/src/Terminal.ts b/src/Terminal.ts index c88a0715..dff303bb 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -278,7 +278,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } this.updateCursorStyle(ev); this.element.classList.add('focus'); - this.showCursor(); + this._showCursor(); this._onFocus.fire(); } @@ -830,7 +830,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT /** * Display the cursor element */ - public showCursor(): void { + private _showCursor(): void { if (!this._coreService.isCursorInitialized) { this._coreService.isCursorInitialized = true; this.refresh(this.buffer.y, this.buffer.y); @@ -1165,7 +1165,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } this._onKey.fire({ key: result.key, domEvent: event }); - this.showCursor(); + this._showCursor(); this._coreService.triggerDataEvent(result.key, true); // Cancel events when not in screen reader mode so events don't get bubbled up and handled by @@ -1242,7 +1242,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT key = String.fromCharCode(key); this._onKey.fire({ key, domEvent: ev }); - this.showCursor(); + this._showCursor(); this._coreService.triggerDataEvent(key, true); return true; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index c15ce560..1cdc4c49 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -190,9 +190,6 @@ export class MockTerminal implements ITerminal { public reset(): void { throw new Error('Method not implemented.'); } - public showCursor(): void { - throw new Error('Method not implemented.'); - } public refresh(start: number, end: number): void { throw new Error('Method not implemented.'); } @@ -215,9 +212,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { public resize(x: number, y: number): void { throw new Error('Method not implemented.'); } - public showCursor(): void { - throw new Error('Method not implemented.'); - } public handler(data: string): void { throw new Error('Method not implemented.'); } diff --git a/src/Types.d.ts b/src/Types.d.ts index 15eda648..2b26c070 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -21,8 +21,6 @@ export type LineData = CharData[]; * InputHandler cleanly from the ITerminal interface. */ export interface IInputHandlingTerminal { - buffers: IBufferSet; - buffer: IBuffer; viewport: IViewport; onA11yCharEmitter: IEventEmitter; @@ -31,7 +29,6 @@ export interface IInputHandlingTerminal { scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; - showCursor(): void; handleTitle(title: string): void; } @@ -143,7 +140,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; - showCursor(): void; } // Portions of the public API that are required by the internal Terminal diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 61ba35ab..d9aaf60a 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; -import { IOptionsService } from 'common/services/Services'; +import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; export class RenderService extends Disposable implements IRenderService { @@ -39,7 +39,9 @@ export class RenderService extends Disposable implements IRenderService { private _rowCount: number, screenElement: HTMLElement, @IOptionsService optionsService: IOptionsService, - @ICharSizeService charSizeService: ICharSizeService + @ICharSizeService charSizeService: ICharSizeService, + @IBufferService private readonly _bufferService: IBufferService, + @ICoreService private readonly _coreService: ICoreService ) { super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); From e9db1ecd1f5dc33967c48935105f574860fa6324 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 16:37:31 -0700 Subject: [PATCH 09/35] Remove Terminal.handleTitle dep --- src/InputHandler.ts | 4 +++- src/Terminal.test.ts | 2 +- src/Terminal.ts | 9 +-------- src/TestUtils.test.ts | 3 --- src/Types.d.ts | 3 ++- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index c3f3446e..f34ef145 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -236,6 +236,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } + private _onTitleChange = new EventEmitter(); + public get onTitleChange(): IEvent { return this._onTitleChange.event; } constructor( private _terminal: IInputHandlingTerminal, @@ -2569,7 +2571,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public setTitle(data: string): void { this._windowTitle = data; - this._terminal.handleTitle(data); + this._onTitleChange.fire(data); } /** diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 40e1b141..61b40d6c 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -121,7 +121,7 @@ describe('Terminal', () => { assert.equal(e, 'title'); done(); }); - term.handleTitle('title'); + term.write('\x1b]2;title\x07'); }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index dff303bb..683437a9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -182,6 +182,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._inputHandler.onRequestReset(() => this.reset()); this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); + this._inputHandler.onTitleChange(title => this._onTitleChange.fire(title)); this.register(this._inputHandler); } @@ -1353,14 +1354,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT // this._onData.fire(data); // } - /** - * Emit the 'title' event and populate the given title. - * @param title The title to populate in the event. - */ - public handleTitle(title: string): void { - this._onTitleChange.fire(title); - } - /** * Reset terminal. * Note: Calling this directly from JS is synchronous but does not clear diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 1cdc4c49..122640ff 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -215,9 +215,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { public handler(data: string): void { throw new Error('Method not implemented.'); } - public handleTitle(title: string): void { - throw new Error('Method not implemented.'); - } } export class MockBuffer implements IBuffer { diff --git a/src/Types.d.ts b/src/Types.d.ts index 2b26c070..0aebc813 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -29,7 +29,6 @@ export interface IInputHandlingTerminal { scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; - handleTitle(title: string): void; } export interface ICompositionHelper { @@ -44,6 +43,8 @@ export interface ICompositionHelper { * Calls the parser and handles actions generated by the parser. */ export interface IInputHandler { + onTitleChange: IEvent; + parse(data: string | Uint8Array): void; print(data: Uint32Array, start: number, end: number): void; From 3897950e22246107f3edc3658aee4497e8f4a9e5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 16:45:46 -0700 Subject: [PATCH 10/35] Remove a11y event dep, introduce forwardEvent helper --- src/InputHandler.ts | 8 ++++++-- src/Terminal.ts | 18 ++++++++++-------- src/Types.d.ts | 3 --- src/common/EventEmitter.ts | 4 ++++ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f34ef145..05d4a981 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -238,6 +238,10 @@ export class InputHandler extends Disposable implements IInputHandler { public get onScroll(): IEvent { return this._onScroll.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } + private _onA11yChar = new EventEmitter(); + public get onA11yChar(): IEvent { return this._onA11yChar.event; } + private _onA11yTab = new EventEmitter(); + public get onA11yTab(): IEvent { return this._onA11yTab.event; } constructor( private _terminal: IInputHandlingTerminal, @@ -520,7 +524,7 @@ export class InputHandler extends Disposable implements IInputHandler { } if (screenReaderMode) { - this._terminal.onA11yCharEmitter.fire(stringFromCodePoint(code)); + this._onA11yChar.fire(stringFromCodePoint(code)); } // insert combining char at last cursor position @@ -738,7 +742,7 @@ export class InputHandler extends Disposable implements IInputHandler { const originalX = this._bufferService.buffer.x; this._bufferService.buffer.x = this._bufferService.buffer.nextStop(); if (this._optionsService.options.screenReaderMode) { - this._terminal.onA11yTabEmitter.fire(this._bufferService.buffer.x - originalX); + this._onA11yTab.fire(this._bufferService.buffer.x - originalX); } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 683437a9..76ebf31f 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -41,7 +41,7 @@ import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; @@ -133,10 +133,10 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT public get onFocus(): IEvent { return this._onFocus.event; } private _onBlur = new EventEmitter(); public get onBlur(): IEvent { return this._onBlur.event; } - public onA11yCharEmitter = new EventEmitter(); - public get onA11yChar(): IEvent { return this.onA11yCharEmitter.event; } - public onA11yTabEmitter = new EventEmitter(); - public get onA11yTab(): IEvent { return this.onA11yTabEmitter.event; } + private _onA11yCharEmitter = new EventEmitter(); + public get onA11yChar(): IEvent { return this._onA11yCharEmitter.event; } + private _onA11yTabEmitter = new EventEmitter(); + public get onA11yTab(): IEvent { return this._onA11yTabEmitter.event; } /** * Creates a new `Terminal` object. @@ -180,9 +180,11 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); - this._inputHandler.onCursorMove(() => this._onCursorMove.fire()); - this._inputHandler.onLineFeed(() => this._onLineFeed.fire()); - this._inputHandler.onTitleChange(title => this._onTitleChange.fire(title)); + forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove); + forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed); + forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange); + forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter); + forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter); this.register(this._inputHandler); } diff --git a/src/Types.d.ts b/src/Types.d.ts index 0aebc813..4e4142b0 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -23,9 +23,6 @@ export type LineData = CharData[]; export interface IInputHandlingTerminal { viewport: IViewport; - onA11yCharEmitter: IEventEmitter; - onA11yTabEmitter: IEventEmitter; - scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 301574e4..847e3249 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -63,3 +63,7 @@ export class EventEmitter implements IEventEmitter { this._disposed = true; } } + +export function forwardEvent(from: IEvent, to: IEventEmitter): void { + from(e => to.fire(e)); +} From 0709b0dc3b5bfe67cd39346ccb716d386e83fc48 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 19:28:00 -0700 Subject: [PATCH 11/35] Break input handler dep on Terminal.scroll --- src/InputHandler.ts | 8 +++++--- src/Terminal.ts | 1 + src/TestUtils.test.ts | 11 ----------- src/Types.d.ts | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 05d4a981..6f160961 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -236,6 +236,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } + private _onScrollRequest = new EventEmitter(); + public get onScrollRequest(): IEvent { return this._onScrollRequest.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } private _onA11yChar = new EventEmitter(); @@ -554,7 +556,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData(), true); + this._onScrollRequest.fire(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; @@ -693,7 +695,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData()); + this._onScrollRequest.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -2676,7 +2678,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._terminal.scroll(this._eraseAttrData()); + this._onScrollRequest.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 76ebf31f..c051f541 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -180,6 +180,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._inputHandler.onRequestBell(() => this.bell()); this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); this._inputHandler.onRequestReset(() => this.reset()); + this._inputHandler.onScrollRequest((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined)); forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove); forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed); forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 122640ff..2027f994 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -14,7 +14,6 @@ import { Terminal } from './Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; -import { EventEmitter } from 'common/EventEmitter'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ISelectionService } from 'browser/services/Services'; @@ -198,23 +197,13 @@ export class MockTerminal implements ITerminal { } export class MockInputHandlingTerminal implements IInputHandlingTerminal { - public onA11yCharEmitter: EventEmitter; - public onA11yTabEmitter: EventEmitter; - public buffers: IBufferSet; - public buffer: IBuffer = new MockBuffer(); public viewport: IViewport; - public scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void { - throw new Error('Method not implemented.'); - } public is(term: string): boolean { throw new Error('Method not implemented.'); } public resize(x: number, y: number): void { throw new Error('Method not implemented.'); } - public handler(data: string): void { - throw new Error('Method not implemented.'); - } } export class MockBuffer implements IBuffer { diff --git a/src/Types.d.ts b/src/Types.d.ts index 4e4142b0..241d8228 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -23,7 +23,6 @@ export type LineData = CharData[]; export interface IInputHandlingTerminal { viewport: IViewport; - scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void; is(term: string): boolean; resize(x: number, y: number): void; } @@ -41,6 +40,7 @@ export interface ICompositionHelper { */ export interface IInputHandler { onTitleChange: IEvent; + onScrollRequest: IEvent; parse(data: string | Uint8Array): void; print(data: Uint32Array, start: number, end: number): void; From a3ac0d542da9bbcfe14a1caed9cfafa0a053c7b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 20:39:55 -0700 Subject: [PATCH 12/35] Remove unused imports --- src/InputHandler.ts | 1 - src/Types.d.ts | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 6f160961..dcdabe95 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -2479,7 +2479,6 @@ export class InputHandler extends Disposable implements IInputHandler { switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t if (rs && second !== 2) { - console.log(rs.dimensions); const w = rs.dimensions.scaledCanvasWidth.toFixed(0); const h = rs.dimensions.scaledCanvasHeight.toFixed(0); this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); diff --git a/src/Types.d.ts b/src/Types.d.ts index 241d8228..047df8ec 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -3,10 +3,10 @@ * @license MIT */ -import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; -import { ICharset, IAttributeData, CharData, CoreMouseEventType, ITerminalOptions } from 'common/Types'; -import { IEvent, IEventEmitter } from 'common/EventEmitter'; -import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; +import { IAttributeData, CharData, ITerminalOptions } from 'common/Types'; +import { IEvent } from 'common/EventEmitter'; +import { ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; From eba372e2cc8d47629f33d4679ebd68de6df83d5d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 25 Apr 2020 20:51:33 -0700 Subject: [PATCH 13/35] More some of Terminal.resize into bufferservice --- src/Terminal.ts | 3 --- src/common/services/BufferService.ts | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c051f541..102cc36e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1292,10 +1292,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT if (x < MINIMUM_COLS) x = MINIMUM_COLS; if (y < MINIMUM_ROWS) y = MINIMUM_ROWS; - this.buffers.resize(x, y); - this._bufferService.resize(x, y); - this.buffers.setupTabStops(this.cols); this._charSizeService?.measure(); diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 1b6da792..a79fc305 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -30,6 +30,8 @@ export class BufferService implements IBufferService { public resize(cols: number, rows: number): void { this.cols = cols; this.rows = rows; + this.buffers.resize(cols, rows); + this.buffers.setupTabStops(this.cols); } public reset(): void { From a6949d522ea702f284f17f19fcc14137d28a3235 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 06:16:13 -0700 Subject: [PATCH 14/35] Move resize event into bufferservice --- src/Terminal.ts | 11 ++++++----- src/common/CoreTerminal.ts | 9 ++++++--- src/common/services/BufferService.ts | 5 +++++ src/common/services/Services.ts | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 102cc36e..41bbd177 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -120,8 +120,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT 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 _onResize = new EventEmitter<{ cols: number, rows: number }>(); - public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } private _onSelectionChange = new EventEmitter(); @@ -1289,19 +1287,22 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT return; } - if (x < MINIMUM_COLS) x = MINIMUM_COLS; - if (y < MINIMUM_ROWS) y = MINIMUM_ROWS; + x = Math.max(x, MINIMUM_COLS); + y = Math.max(y, MINIMUM_ROWS); this._bufferService.resize(x, y); + // TODO: Have charsize, viewport and renderservice depend on IBufferService.onResize? + this._charSizeService?.measure(); + // TODO: Call on resize event? // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location this.viewport?.syncScrollArea(true); + // TODO: Move to renderservice this.refresh(0, this.rows - 1); - this._onResize.fire({ cols: x, rows: y }); } /** diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 5428a3bc..fd1d9309 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -29,7 +29,7 @@ import { BufferService } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; import { ITerminalOptions, IDisposable } from './Types'; import { CoreService } from 'common/services/CoreService'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; import { DirtyRowService } from 'common/services/DirtyRowService'; import { UnicodeService } from 'common/services/UnicodeService'; @@ -58,6 +58,8 @@ export abstract class CoreTerminal extends Disposable { public get onData(): IEvent { return this._onData.event; } protected _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } + private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } @@ -88,8 +90,9 @@ export abstract class CoreTerminal extends Disposable { this._instantiationService.setService(ICharsetService, this._charsetService); // Setup listeners - this._coreService.onData(e => this._onData.fire(e)); - this._coreService.onBinary(e => this._onBinary.fire(e)); + forwardEvent(this._bufferService.onResize, this._onResize); + forwardEvent(this._coreService.onData, this._onData); + forwardEvent(this._coreService.onBinary, this._onBinary); this.optionsService.onOptionChange(key => this._updateOptions(key)); } diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index a79fc305..d62d2cb0 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,6 +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'; export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars export const MINIMUM_ROWS = 1; @@ -17,6 +18,9 @@ export class BufferService implements IBufferService { public rows: number; public buffers: IBufferSet; + private _onResize = new EventEmitter<{ cols: number, rows: number }>(); + public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; } + public get buffer(): IBuffer { return this.buffers.active; } constructor( @@ -32,6 +36,7 @@ export class BufferService implements IBufferService { this.rows = rows; this.buffers.resize(cols, rows); this.buffers.setupTabStops(this.cols); + this._onResize.fire({ cols, rows }); } public reset(): void { diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 05a6f3ae..8fa3f57b 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -17,7 +17,7 @@ export interface IBufferService { readonly buffer: IBuffer; readonly buffers: IBufferSet; - // TODO: Move resize event here + onResize: IEvent<{ cols: number, rows: number }>; resize(cols: number, rows: number): void; reset(): void; From 451d9aab96df86f077ef39000490dc62ef654ce8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 06:21:16 -0700 Subject: [PATCH 15/35] Register forwardEvent calls --- src/Terminal.ts | 18 +++++++++--------- src/common/CoreTerminal.ts | 8 ++++---- src/common/EventEmitter.ts | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 41bbd177..9572345e 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -175,15 +175,15 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT } else { // Register input handler and refire/handle events this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); - this._inputHandler.onRequestBell(() => this.bell()); - this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)); - this._inputHandler.onRequestReset(() => this.reset()); - this._inputHandler.onScrollRequest((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined)); - forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove); - forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed); - forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange); - forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter); - forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter); + this.register(this._inputHandler.onRequestBell(() => this.bell())); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); + this.register(this._inputHandler.onRequestReset(() => this.reset())); + this.register(this._inputHandler.onScrollRequest((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); + this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); + this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + 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(this._inputHandler); } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index fd1d9309..90cc7ade 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -90,10 +90,10 @@ export abstract class CoreTerminal extends Disposable { this._instantiationService.setService(ICharsetService, this._charsetService); // Setup listeners - forwardEvent(this._bufferService.onResize, this._onResize); - forwardEvent(this._coreService.onData, this._onData); - forwardEvent(this._coreService.onBinary, this._onBinary); - this.optionsService.onOptionChange(key => this._updateOptions(key)); + 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.optionsService.onOptionChange(key => this._updateOptions(key))); } public dispose(): void { diff --git a/src/common/EventEmitter.ts b/src/common/EventEmitter.ts index 847e3249..4684809f 100644 --- a/src/common/EventEmitter.ts +++ b/src/common/EventEmitter.ts @@ -64,6 +64,6 @@ export class EventEmitter implements IEventEmitter { } } -export function forwardEvent(from: IEvent, to: IEventEmitter): void { - from(e => to.fire(e)); +export function forwardEvent(from: IEvent, to: IEventEmitter): IDisposable { + return from(e => to.fire(e)); } From 3a1a0d659069e5abbaf060da3f1f2f6f930ded88 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 06:34:36 -0700 Subject: [PATCH 16/35] Remove input handler Terminal.resize dep --- src/InputHandler.ts | 4 ++-- src/Terminal.ts | 21 ++++++--------------- src/Types.d.ts | 1 - src/browser/services/RenderService.ts | 4 ++-- src/common/CoreTerminal.ts | 13 ++++++++++++- src/common/TestUtils.test.ts | 1 + 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index dcdabe95..de643314 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1741,7 +1741,7 @@ export class InputHandler extends Disposable implements IInputHandler { * through `options.windowsOptions`. */ if (this._optionsService.options.windowOptions.setWinLines) { - this._terminal.resize(132, this._bufferService.rows); + this._bufferService.resize(132, this._bufferService.rows); this._onRequestReset.fire(); } break; @@ -1968,7 +1968,7 @@ export class InputHandler extends Disposable implements IInputHandler { * through `options.windowsOptions`. */ if (this._optionsService.options.windowOptions.setWinLines) { - this._terminal.resize(80, this._bufferService.rows); + this._bufferService.resize(80, this._bufferService.rows); this._onRequestReset.fire(); } break; diff --git a/src/Terminal.ts b/src/Terminal.ts index 9572345e..6e192320 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,6 @@ import { ColorManager } from 'browser/ColorManager'; import { RenderService } from 'browser/services/RenderService'; import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services'; import { CharSizeService } from 'browser/services/CharSizeService'; -import { MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; @@ -155,6 +154,9 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._setup(); + // Setup listeners + this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)); + this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); } @@ -1275,10 +1277,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT * @param y The number of rows to resize to. */ public resize(x: number, y: number): void { - if (isNaN(x) || isNaN(y)) { - return; - } - if (x === this.cols && y === this.rows) { // Check if we still need to measure the char size (fixes #785). if (this._charSizeService && !this._charSizeService.hasValidSize) { @@ -1287,22 +1285,15 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT return; } - x = Math.max(x, MINIMUM_COLS); - y = Math.max(y, MINIMUM_ROWS); - - this._bufferService.resize(x, y); - - // TODO: Have charsize, viewport and renderservice depend on IBufferService.onResize? + super.resize(x, y); + } + private _afterResize(x: number, y: number): void { this._charSizeService?.measure(); - // TODO: Call on resize event? // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an // invalid location this.viewport?.syncScrollArea(true); - - // TODO: Move to renderservice - this.refresh(0, this.rows - 1); } /** diff --git a/src/Types.d.ts b/src/Types.d.ts index 047df8ec..c3e1c53d 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -24,7 +24,6 @@ export interface IInputHandlingTerminal { viewport: IViewport; is(term: string): boolean; - resize(x: number, y: number): void; } export interface ICompositionHelper { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index d9aaf60a..27b66458 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -40,8 +40,7 @@ export class RenderService extends Disposable implements IRenderService { screenElement: HTMLElement, @IOptionsService optionsService: IOptionsService, @ICharSizeService charSizeService: ICharSizeService, - @IBufferService private readonly _bufferService: IBufferService, - @ICoreService private readonly _coreService: ICoreService + @IBufferService private readonly _bufferService: IBufferService ) { super(); this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end)); @@ -51,6 +50,7 @@ export class RenderService extends Disposable implements IRenderService { this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); + this.register(this._bufferService.onResize(e => this._fullRefresh())); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged())); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 90cc7ade..49f3509d 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -25,7 +25,7 @@ import { Disposable } from 'common/Lifecycle'; import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService } from 'common/services/Services'; import { InstantiationService } from 'common/services/InstantiationService'; import { LogService } from 'common/services/LogService'; -import { BufferService } from 'common/services/BufferService'; +import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService'; import { OptionsService } from 'common/services/OptionsService'; import { ITerminalOptions, IDisposable } from './Types'; import { CoreService } from 'common/services/CoreService'; @@ -105,6 +105,17 @@ export abstract class CoreTerminal extends Disposable { this._windowsMode = undefined; } + public resize(x: number, y: number): void { + if (isNaN(x) || isNaN(y)) { + return; + } + + x = Math.max(x, MINIMUM_COLS); + y = Math.max(y, MINIMUM_ROWS); + + this._bufferService.resize(x, y); + } + protected _setup(): void { if (this.optionsService.options.windowsMode) { this._enableWindowsMode(); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index e5b3a425..eacf92fe 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -16,6 +16,7 @@ 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; constructor( public cols: number, public rows: number, From dbced5c1a442b60113083ca8c7099a7f9990f2b4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 06:38:10 -0700 Subject: [PATCH 17/35] Move Terminal.is into InputHandler --- src/InputHandler.ts | 20 ++++++++++++++------ src/Terminal.ts | 8 -------- src/Types.d.ts | 2 -- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index de643314..f4a084e2 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -1528,9 +1528,9 @@ export class InputHandler extends Disposable implements IInputHandler { if (params.params[0] > 0) { return; } - if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) { + if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[?1;2c'); - } else if (this._terminal.is('linux')) { + } else if (this._is('linux')) { this._coreService.triggerDataEvent(C0.ESC + '[?6c'); } } @@ -1566,19 +1566,27 @@ export class InputHandler extends Disposable implements IInputHandler { // xterm and urxvt // seem to spit this // out around ~370 times (?). - if (this._terminal.is('xterm')) { + if (this._is('xterm')) { this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c'); - } else if (this._terminal.is('rxvt-unicode')) { + } else if (this._is('rxvt-unicode')) { this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c'); - } else if (this._terminal.is('linux')) { + } else if (this._is('linux')) { // not supported by linux console. // linux console echoes parameters. this._coreService.triggerDataEvent(params.params[0] + 'c'); - } else if (this._terminal.is('screen')) { + } else if (this._is('screen')) { this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c'); } } + /** + * Evaluate if the current terminal is the given argument. + * @param term The terminal name to evaluate + */ + private _is(term: string): boolean { + return (this._optionsService.options.termName + '').indexOf(term) === 0; + } + /** * CSI Pm h Set Mode (SM). * Ps = 2 -> Keyboard Action Mode (AM). diff --git a/src/Terminal.ts b/src/Terminal.ts index 6e192320..9dab9651 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1316,14 +1316,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._onScroll.fire(this.buffer.ydisp); } - /** - * Evaluate if the current terminal is the given argument. - * @param term The terminal name to evaluate - */ - public is(term: string): boolean { - return (this.options.termName + '').indexOf(term) === 0; - } - /** * Emit the data event and populate the given data. * @param data The data to populate in the event. diff --git a/src/Types.d.ts b/src/Types.d.ts index c3e1c53d..7861fc7f 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -22,8 +22,6 @@ export type LineData = CharData[]; */ export interface IInputHandlingTerminal { viewport: IViewport; - - is(term: string): boolean; } export interface ICompositionHelper { From ba40fe4ff4a16e794190e8876fb3707cc086e13c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 06:46:37 -0700 Subject: [PATCH 18/35] Remove Terminal dependency from InputHandler :tada: --- src/InputHandler.test.ts | 29 ++++++++++------------------- src/InputHandler.ts | 23 ++++++++++++----------- src/Terminal.ts | 29 ++++------------------------- src/TestUtils.test.ts | 12 +----------- src/Types.d.ts | 9 --------- 5 files changed, 27 insertions(+), 75 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 6c5b9cbb..fd07fcb2 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,7 +5,7 @@ import { assert, expect } from 'chai'; import { InputHandler } from './InputHandler'; -import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test'; +import { TestTerminal } from './TestUtils.test'; import { Terminal } from './Terminal'; import { IBufferLine, IAttributeData } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -39,12 +39,11 @@ class TestInputHandler extends InputHandler { describe('InputHandler', () => { describe('save and restore cursor', () => { - const terminal = new MockInputHandlingTerminal(); const bufferService = new MockBufferService(80, 30); bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); @@ -64,7 +63,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -103,9 +102,8 @@ describe('InputHandler', () => { }); describe('setMode', () => { it('should toggle bracketedPasteMode', () => { - const terminal = new MockInputHandlingTerminal(); const coreService = new MockCoreService(); - const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -122,9 +120,8 @@ describe('InputHandler', () => { } it('insertChars', function(): void { - const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -160,9 +157,8 @@ describe('InputHandler', () => { expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); }); it('deleteChars', function(): void { - const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -201,9 +197,8 @@ describe('InputHandler', () => { expect(line1.translateToString(true)).equals(Array(bufferService.cols - 9).join('a')); }); it('eraseInLine', function(): void { - const term = new Terminal(); const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -230,9 +225,8 @@ describe('InputHandler', () => { }); it('eraseInDisplay', function(): void { - const term = new Terminal({cols: 80, rows: 7}); const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -366,8 +360,7 @@ describe('InputHandler', () => { }); describe('print', () => { it('should not cause an infinite loop (regression test)', () => { - const term = new Terminal(); - const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -375,14 +368,12 @@ describe('InputHandler', () => { }); describe('alt screen', () => { - let term: Terminal; let bufferService: IBufferService; let handler: InputHandler; beforeEach(() => { - term = new Terminal(); bufferService = new MockBufferService(80, 30); - handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + handler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index f4a084e2..bf05d511 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler, IInputHandlingTerminal } from './Types'; +import { IInputHandler } from './Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -238,6 +238,8 @@ export class InputHandler extends Disposable implements IInputHandler { public get onScroll(): IEvent { return this._onScroll.event; } private _onScrollRequest = new EventEmitter(); public get onScrollRequest(): IEvent { return this._onScrollRequest.event; } + private _onSyncScrollBarRequest = new EventEmitter(); + public get onSyncScrollBarRequest(): IEvent { return this._onSyncScrollBarRequest.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } private _onA11yChar = new EventEmitter(); @@ -246,7 +248,6 @@ export class InputHandler extends Disposable implements IInputHandler { public get onA11yTab(): IEvent { return this._onA11yTab.event; } constructor( - private _terminal: IInputHandlingTerminal, private readonly _bufferService: IBufferService, private readonly _charsetService: ICharsetService, private readonly _coreService: ICoreService, @@ -256,8 +257,8 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _coreMouseService: ICoreMouseService, private readonly _unicodeService: IUnicodeService, private readonly _instantiationService: IInstantiationService, - private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()) - { + private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser() + ) { super(); this.register(this._parser); @@ -1766,7 +1767,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1812,7 +1813,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -1993,7 +1994,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2030,7 +2031,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2369,7 +2370,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2617,7 +2618,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): void { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); } /** @@ -2628,7 +2629,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): void { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._terminal.viewport?.syncScrollArea(); + this._onSyncScrollBarRequest.fire(); } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index 9dab9651..4606f1aa 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -60,7 +60,7 @@ import { CoreTerminal } from 'common/CoreTerminal'; const document = (typeof window !== 'undefined') ? window.document : null; -export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingTerminal { +export class Terminal extends CoreTerminal implements ITerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; public screenElement: HTMLElement; @@ -176,7 +176,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestReset(() => this.reset())); @@ -462,6 +462,7 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._viewportScrollArea ); this.viewport.onThemeChange(this._colorManager.colors); + this.register(this._inputHandler.onSyncScrollBarRequest(() => this.viewport.syncScrollArea())); this.register(this.viewport); this.register(this.onCursorMove(() => this._renderService.onCursorMove())); @@ -1316,28 +1317,6 @@ export class Terminal extends CoreTerminal implements ITerminal, IInputHandlingT this._onScroll.fire(this.buffer.ydisp); } - /** - * Emit the data event and populate the given data. - * @param data The data to populate in the event. - */ - // public handler(data: string): void { - // // Prevents all events to pty process if stdin is disabled - // if (this.options.disableStdin) { - // return; - // } - - // // Clear the selection if the selection manager is available and has an active selection - // if (this.selectionService && this.selectionService.hasSelection) { - // this.selectionService.clearSelection(); - // } - - // // Input is being sent to the terminal, the terminal should focus the prompt. - // if (this.buffer.ybase !== this.buffer.ydisp) { - // this.scrollToBottom(); - // } - // this._onData.fire(data); - // } - /** * Reset terminal. * Note: Calling this directly from JS is synchronous but does not clear diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 2027f994..32b2633f 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,7 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser } from './Types'; +import { ICompositionHelper, ITerminal, IBrowser } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -196,16 +196,6 @@ export class MockTerminal implements ITerminal { public deregisterCharacterJoiner(joinerId: number): void { } } -export class MockInputHandlingTerminal implements IInputHandlingTerminal { - public viewport: IViewport; - public is(term: string): boolean { - throw new Error('Method not implemented.'); - } - public resize(x: number, y: number): void { - throw new Error('Method not implemented.'); - } -} - export class MockBuffer implements IBuffer { public markers: IMarker[]; public addMarker(y: number): IMarker { diff --git a/src/Types.d.ts b/src/Types.d.ts index 7861fc7f..e9a2199d 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -15,15 +15,6 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; export type LineData = CharData[]; -/** - * This interface encapsulates everything needed from the Terminal by the - * InputHandler. This cleanly separates the large amount of methods needed by - * InputHandler cleanly from the ITerminal interface. - */ -export interface IInputHandlingTerminal { - viewport: IViewport; -} - export interface ICompositionHelper { compositionstart(): void; compositionupdate(ev: CompositionEvent): void; From 81d289b266cbba584be752c010b4d1584d32ca4a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 07:03:11 -0700 Subject: [PATCH 19/35] Remove RenderService dep in InputHandler --- src/InputHandler.test.ts | 18 +++++------ src/InputHandler.ts | 65 +++++++++++++++++++--------------------- src/Terminal.ts | 28 ++++++++++++++--- src/Types.d.ts | 2 +- 4 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index fd07fcb2..37a54e39 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -43,7 +43,7 @@ describe('InputHandler', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); @@ -63,7 +63,7 @@ describe('InputHandler', () => { describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); @@ -103,7 +103,7 @@ describe('InputHandler', () => { describe('setMode', () => { it('should toggle bracketedPasteMode', () => { const coreService = new MockCoreService(); - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // Set bracketed paste mode inputHandler.setModePrivate(Params.fromArray([2004])); assert.equal(coreService.decPrivateModes.bracketedPasteMode, true); @@ -121,7 +121,7 @@ describe('InputHandler', () => { it('insertChars', function(): void { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -158,7 +158,7 @@ describe('InputHandler', () => { }); it('deleteChars', function(): void { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // insert some data in first and second line inputHandler.parse(Array(bufferService.cols - 9).join('a')); @@ -198,7 +198,7 @@ describe('InputHandler', () => { }); it('eraseInLine', function(): void { const bufferService = new MockBufferService(80, 30); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // fill 6 lines to test 3 different states inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -226,7 +226,7 @@ describe('InputHandler', () => { }); it('eraseInDisplay', function(): void { const bufferService = new MockBufferService(80, 7); - const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); // fill display with a's for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a')); @@ -360,7 +360,7 @@ describe('InputHandler', () => { }); describe('print', () => { it('should not cause an infinite loop (regression test)', () => { - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); const container = new Uint32Array(10); container[0] = 0x200B; inputHandler.print(container, 0, 1); @@ -373,7 +373,7 @@ describe('InputHandler', () => { beforeEach(() => { bufferService = new MockBufferService(80, 30); - handler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any); + handler = new InputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); }); it('should handle DECSET/DECRST 47 (alt screen buffer)', () => { handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST'); diff --git a/src/InputHandler.ts b/src/InputHandler.ts index bf05d511..61ef5546 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -18,10 +18,9 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; -import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services'; +import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; -import { IRenderService } from 'browser/services/Services'; /** * Map collect to glevel. Used in `selectCharset`. @@ -94,7 +93,10 @@ function paramToWindowOption(n: number, opts: IWindowOptions): boolean { return false; } - +export enum WindowsOptionsReportType { + GET_WIN_SIZE_PIXELS = 0, + GET_CELL_SIZE_PIXELS = 1 +} /** * DCS subparser implementations @@ -224,28 +226,31 @@ export class InputHandler extends Disposable implements IInputHandler { private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone(); private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone(); + private _onRequestBell = new EventEmitter(); + public get onRequestBell(): IEvent { return this._onRequestBell.event; } private _onRequestRefreshRows = new EventEmitter(); public get onRequestRefreshRows(): IEvent { return this._onRequestRefreshRows.event; } private _onRequestReset = new EventEmitter(); public get onRequestReset(): IEvent { return this._onRequestReset.event; } - private _onRequestBell = new EventEmitter(); - public get onRequestBell(): IEvent { return this._onRequestBell.event; } + private _onRequestScroll = new EventEmitter(); + public get onRequestScroll(): IEvent { return this._onRequestScroll.event; } + private _onRequestSyncScrollBar = new EventEmitter(); + public get onRequestSyncScrollBar(): IEvent { return this._onRequestSyncScrollBar.event; } + private _onRequestWindowsOptionsReport = new EventEmitter(); + public get onRequestWindowsOptionsReport(): IEvent { return this._onRequestWindowsOptionsReport.event; } + + private _onA11yChar = new EventEmitter(); + public get onA11yChar(): IEvent { return this._onA11yChar.event; } + private _onA11yTab = new EventEmitter(); + public get onA11yTab(): IEvent { return this._onA11yTab.event; } private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onLineFeed = new EventEmitter(); public get onLineFeed(): IEvent { return this._onLineFeed.event; } private _onScroll = new EventEmitter(); public get onScroll(): IEvent { return this._onScroll.event; } - private _onScrollRequest = new EventEmitter(); - public get onScrollRequest(): IEvent { return this._onScrollRequest.event; } - private _onSyncScrollBarRequest = new EventEmitter(); - public get onSyncScrollBarRequest(): IEvent { return this._onSyncScrollBarRequest.event; } private _onTitleChange = new EventEmitter(); public get onTitleChange(): IEvent { return this._onTitleChange.event; } - private _onA11yChar = new EventEmitter(); - public get onA11yChar(): IEvent { return this._onA11yChar.event; } - private _onA11yTab = new EventEmitter(); - public get onA11yTab(): IEvent { return this._onA11yTab.event; } constructor( private readonly _bufferService: IBufferService, @@ -256,7 +261,6 @@ export class InputHandler extends Disposable implements IInputHandler { private readonly _optionsService: IOptionsService, private readonly _coreMouseService: ICoreMouseService, private readonly _unicodeService: IUnicodeService, - private readonly _instantiationService: IInstantiationService, private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser() ) { super(); @@ -557,7 +561,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onScrollRequest.fire(this._eraseAttrData(), true); + this._onRequestScroll.fire(this._eraseAttrData(), true); } else { if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; @@ -696,7 +700,7 @@ export class InputHandler extends Disposable implements IInputHandler { buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onScrollRequest.fire(this._eraseAttrData()); + this._onRequestScroll.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } @@ -1767,7 +1771,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -1813,7 +1817,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffers.activateAltBuffer(this._eraseAttrData()); this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = true; @@ -1994,7 +1998,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 66: this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); break; case 9: // X10 Mouse case 1000: // vt200 mouse @@ -2031,7 +2035,7 @@ export class InputHandler extends Disposable implements IInputHandler { } this._coreService.isCursorInitialized = true; this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1); - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); break; case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste) this._coreService.decPrivateModes.bracketedPasteMode = false; @@ -2370,7 +2374,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public softReset(params: IParams): void { this._coreService.isCursorHidden = false; - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); this._bufferService.buffer.scrollTop = 0; this._bufferService.buffer.scrollBottom = this._bufferService.rows - 1; this._curAttrData = DEFAULT_ATTR_DATA.clone(); @@ -2484,21 +2488,14 @@ export class InputHandler extends Disposable implements IInputHandler { return; } const second = (params.length > 1) ? params.params[1] : 0; - const rs = this._instantiationService.getService(IRenderService); switch (params.params[0]) { case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t - if (rs && second !== 2) { - const w = rs.dimensions.scaledCanvasWidth.toFixed(0); - const h = rs.dimensions.scaledCanvasHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`); + if (second !== 2) { + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS); } break; case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t - if (rs) { - const w = rs.dimensions.scaledCellWidth.toFixed(0); - const h = rs.dimensions.scaledCellHeight.toFixed(0); - this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`); - } + this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS); break; case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t if (this._bufferService) { @@ -2618,7 +2615,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadApplicationMode(): void { this._logService.debug('Serial port requested application keypad.'); this._coreService.decPrivateModes.applicationKeypad = true; - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); } /** @@ -2629,7 +2626,7 @@ export class InputHandler extends Disposable implements IInputHandler { public keypadNumericMode(): void { this._logService.debug('Switching back to normal keypad.'); this._coreService.decPrivateModes.applicationKeypad = false; - this._onSyncScrollBarRequest.fire(); + this._onRequestSyncScrollBar.fire(); } /** @@ -2686,7 +2683,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._bufferService.buffer.y++; if (buffer.y === buffer.scrollBottom + 1) { buffer.y--; - this._onScrollRequest.fire(this._eraseAttrData()); + this._onRequestScroll.fire(this._eraseAttrData()); } else if (buffer.y >= this._bufferService.rows) { buffer.y = this._bufferService.rows - 1; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 4606f1aa..e3af555b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -27,7 +27,7 @@ import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; -import { InputHandler } from './InputHandler'; +import { InputHandler, WindowsOptionsReportType } from './InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; @@ -176,11 +176,12 @@ export class Terminal extends CoreTerminal implements ITerminal { this._inputHandler.reset(); } else { // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService); + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(this._inputHandler.onScrollRequest((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); + this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); + this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange)); @@ -462,7 +463,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportScrollArea ); this.viewport.onThemeChange(this._colorManager.colors); - this.register(this._inputHandler.onSyncScrollBarRequest(() => this.viewport.syncScrollArea())); + this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport.syncScrollArea())); this.register(this.viewport); this.register(this.onCursorMove(() => this._renderService.onCursorMove())); @@ -1351,6 +1352,25 @@ export class Terminal extends CoreTerminal implements ITerminal { this.viewport?.syncScrollArea(); } + private _reportWindowsOptions(type: WindowsOptionsReportType): void { + if (!this._renderService) { + return; + } + + switch (type) { + case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS: + const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0); + const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`); + break; + case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS: + const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0); + const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0); + this._coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`); + break; + } + } + // TODO: Remove cancel function and cancelEvents option public cancel(ev: Event, force?: boolean): boolean { if (!this.options.cancelEvents && !force) { diff --git a/src/Types.d.ts b/src/Types.d.ts index e9a2199d..2f57e99c 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -28,7 +28,7 @@ export interface ICompositionHelper { */ export interface IInputHandler { onTitleChange: IEvent; - onScrollRequest: IEvent; + onRequestScroll: IEvent; parse(data: string | Uint8Array): void; print(data: Uint32Array, start: number, end: number): void; From d756086b8edc0fc31582499012d7a50dd85f6c3f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 07:24:30 -0700 Subject: [PATCH 20/35] Move InputHandler into common Part of #2749 Part of #1507 --- src/InputHandler.test.ts | 2 +- src/Terminal.ts | 2 +- src/Types.d.ts | 85 -------------------------- src/{ => common}/InputHandler.ts | 36 +++++------ src/common/TestUtils.test.ts | 1 - src/common/Types.d.ts | 86 +++++++++++++++++++++++++++ src/common/buffer/Buffer.ts | 2 +- src/common/buffer/Types.d.ts | 2 +- src/common/data/Charsets.ts | 6 +- src/common/services/CharsetService.ts | 11 ++-- src/common/services/Services.ts | 3 +- 11 files changed, 119 insertions(+), 117 deletions(-) rename src/{ => common}/InputHandler.ts (99%) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 37a54e39..5a3dc2b5 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -4,7 +4,7 @@ */ import { assert, expect } from 'chai'; -import { InputHandler } from './InputHandler'; +import { InputHandler } from './common/InputHandler'; import { TestTerminal } from './TestUtils.test'; import { Terminal } from './Terminal'; import { IBufferLine, IAttributeData } from 'common/Types'; diff --git a/src/Terminal.ts b/src/Terminal.ts index e3af555b..61c997bd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -27,7 +27,7 @@ import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; -import { InputHandler, WindowsOptionsReportType } from './InputHandler'; +import { InputHandler, WindowsOptionsReportType } from './common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; diff --git a/src/Types.d.ts b/src/Types.d.ts index 2f57e99c..05c89e15 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -23,91 +23,6 @@ export interface ICompositionHelper { keydown(ev: KeyboardEvent): boolean; } -/** - * Calls the parser and handles actions generated by the parser. - */ -export interface IInputHandler { - onTitleChange: IEvent; - onRequestScroll: IEvent; - - parse(data: string | Uint8Array): void; - print(data: Uint32Array, start: number, end: number): void; - - /** C0 BEL */ bell(): void; - /** C0 LF */ lineFeed(): void; - /** C0 CR */ carriageReturn(): void; - /** C0 BS */ backspace(): void; - /** C0 HT */ tab(): void; - /** C0 SO */ shiftOut(): void; - /** C0 SI */ shiftIn(): void; - - /** CSI @ */ insertChars(params: IParams): void; - /** CSI SP @ */ scrollLeft(params: IParams): void; - /** CSI A */ cursorUp(params: IParams): void; - /** CSI SP A */ scrollRight(params: IParams): void; - /** CSI B */ cursorDown(params: IParams): void; - /** CSI C */ cursorForward(params: IParams): void; - /** CSI D */ cursorBackward(params: IParams): void; - /** CSI E */ cursorNextLine(params: IParams): void; - /** CSI F */ cursorPrecedingLine(params: IParams): void; - /** CSI G */ cursorCharAbsolute(params: IParams): void; - /** CSI H */ cursorPosition(params: IParams): void; - /** CSI I */ cursorForwardTab(params: IParams): void; - /** CSI J */ eraseInDisplay(params: IParams): void; - /** CSI K */ eraseInLine(params: IParams): void; - /** CSI L */ insertLines(params: IParams): void; - /** CSI M */ deleteLines(params: IParams): void; - /** CSI P */ deleteChars(params: IParams): void; - /** CSI S */ scrollUp(params: IParams): void; - /** CSI T */ scrollDown(params: IParams, collect?: string): void; - /** CSI X */ eraseChars(params: IParams): void; - /** CSI Z */ cursorBackwardTab(params: IParams): void; - /** CSI ` */ charPosAbsolute(params: IParams): void; - /** CSI a */ hPositionRelative(params: IParams): void; - /** CSI b */ repeatPrecedingCharacter(params: IParams): void; - /** CSI c */ sendDeviceAttributesPrimary(params: IParams): void; - /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void; - /** CSI d */ linePosAbsolute(params: IParams): void; - /** CSI e */ vPositionRelative(params: IParams): void; - /** CSI f */ hVPosition(params: IParams): void; - /** CSI g */ tabClear(params: IParams): void; - /** CSI h */ setMode(params: IParams, collect?: string): void; - /** CSI l */ resetMode(params: IParams, collect?: string): void; - /** CSI m */ charAttributes(params: IParams): void; - /** CSI n */ deviceStatus(params: IParams, collect?: string): void; - /** CSI p */ softReset(params: IParams, collect?: string): void; - /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; - /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; - /** CSI s */ saveCursor(params: IParams): void; - /** CSI u */ restoreCursor(params: IParams): void; - /** CSI ' } */ insertColumns(params: IParams): void; - /** CSI ' ~ */ deleteColumns(params: IParams): void; - /** OSC 0 - OSC 2 */ setTitle(data: string): void; - /** ESC E */ nextLine(): void; - /** ESC = */ keypadApplicationMode(): void; - /** ESC > */ keypadNumericMode(): void; - /** ESC % G - ESC % @ */ selectDefaultCharset(): void; - /** ESC ( C - ESC ) C - ESC * C - ESC + C - ESC - C - ESC . C - ESC / C */ selectCharset(collectAndFlag: string): void; - /** ESC D */ index(): void; - /** ESC H */ tabSet(): void; - /** ESC M */ reverseIndex(): void; - /** ESC c */ fullReset(): void; - /** ESC n - ESC o - ESC | - ESC } - ESC ~ */ setgLevel(level: number): void; - /** ESC # 8 */ screenAlignmentPattern(): void; -} - export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { screenElement: HTMLElement; browser: IBrowser; diff --git a/src/InputHandler.ts b/src/common/InputHandler.ts similarity index 99% rename from src/InputHandler.ts rename to src/common/InputHandler.ts index 61ef5546..70ec8859 100644 --- a/src/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -4,7 +4,7 @@ * @license MIT */ -import { IInputHandler } from './Types'; +import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; import { C0, C1 } from 'common/data/EscapeSequences'; import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets'; import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser'; @@ -17,7 +17,6 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types'; import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services'; import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; @@ -504,7 +503,7 @@ export class InputHandler extends Disposable implements IInputHandler { const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._coreService.modes.insertMode; const curAttr = this._curAttrData; - let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + let bufferRow = buffer.lines.get(buffer.y + buffer.ybase)!; this._dirtyRowService.markDirty(buffer.y); @@ -568,10 +567,10 @@ export class InputHandler extends Disposable implements IInputHandler { } // The line already exists (eg. the initial viewport), mark it as a // wrapped line - buffer.lines.get(buffer.y).isWrapped = true; + buffer.lines.get(buffer.y)!.isWrapped = true; } // row changed, get it again - bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + bufferRow = buffer.lines.get(buffer.y + buffer.ybase)!; } else { buffer.x = cols - 1; if (chWidth === 2) { @@ -1037,7 +1036,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param end end - 1 is last erased cell */ private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void { - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!; line.replaceCells( start, end, @@ -1055,7 +1054,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param y row index */ private _resetBufferLine(y: number): void { - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + y)!; line.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData())); line.isWrapped = false; } @@ -1104,7 +1103,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(j, 0, this._bufferService.buffer.x + 1, true); if (this._bufferService.buffer.x + 1 >= this._bufferService.cols) { // Deleted entire previous line. This next line can no longer be wrapped. - this._bufferService.buffer.lines.get(j + 1).isWrapped = false; + this._bufferService.buffer.lines.get(j + 1)!.isWrapped = false; } while (j--) { this._resetBufferLine(j); @@ -1356,7 +1355,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.deleteCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1389,7 +1388,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.insertCells(0, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1412,7 +1411,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = this._bufferService.buffer.lines.get(buffer.ybase + y); + const line = this._bufferService.buffer.lines.get(buffer.ybase + y)!; line.insertCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -1435,7 +1434,7 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = buffer.scrollTop; y <= buffer.scrollBottom; ++y) { - const line = buffer.lines.get(buffer.ybase + y); + const line = buffer.lines.get(buffer.ybase + y)!; line.deleteCells(buffer.x, param, buffer.getNullCell(this._eraseAttrData()), this._eraseAttrData()); line.isWrapped = false; } @@ -2064,7 +2063,7 @@ export class InputHandler extends Disposable implements IInputHandler { do { accu[advance + cSpace] = params.params[pos + advance]; if (params.hasSubParams(pos + advance)) { - const subparams = params.getSubParams(pos + advance); + const subparams = params.getSubParams(pos + advance)!; let i = 0; do { if (accu[1] === 5) { @@ -2519,12 +2518,12 @@ export class InputHandler extends Disposable implements IInputHandler { case 23: // PopTitle if (second === 0 || second === 2) { if (this._windowTitleStack.length) { - this.setTitle(this._windowTitleStack.pop()); + this.setTitle(this._windowTitleStack.pop()!); } } if (second === 0 || second === 1) { if (this._iconNameStack.length) { - this.setIconName(this._iconNameStack.pop()); + this.setIconName(this._iconNameStack.pop()!); } } break; @@ -2788,8 +2787,11 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) { const row = buffer.y + buffer.ybase + yOffset; - buffer.lines.get(row).fill(cell); - buffer.lines.get(row).isWrapped = false; + const line = buffer.lines.get(row); + if (line) { + line.fill(cell); + line.isWrapped = false; + } } this._dirtyRowService.markAllDirty(); this._setCursor(0, 0); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index eacf92fe..55e34084 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -49,7 +49,6 @@ export class MockCharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; public glevel: number = 0; - public charsets: ReadonlyArray = []; public reset(): void {} public setgLevel(g: number): void {} public setgCharset(g: number, charset: ICharset): void {} diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index cc7928c3..49a723b2 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -6,6 +6,7 @@ import { ITerminalOptions as IPublicTerminalOptions } from 'xterm'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; +import { IParams } from 'common/parser/Types'; export interface IDisposable { dispose(): void; @@ -300,3 +301,88 @@ export interface IWindowOptions { popTitle?: boolean; setWinLines?: boolean; } + +/** + * Calls the parser and handles actions generated by the parser. + */ +export interface IInputHandler { + onTitleChange: IEvent; + onRequestScroll: IEvent; + + parse(data: string | Uint8Array): void; + print(data: Uint32Array, start: number, end: number): void; + + /** C0 BEL */ bell(): void; + /** C0 LF */ lineFeed(): void; + /** C0 CR */ carriageReturn(): void; + /** C0 BS */ backspace(): void; + /** C0 HT */ tab(): void; + /** C0 SO */ shiftOut(): void; + /** C0 SI */ shiftIn(): void; + + /** CSI @ */ insertChars(params: IParams): void; + /** CSI SP @ */ scrollLeft(params: IParams): void; + /** CSI A */ cursorUp(params: IParams): void; + /** CSI SP A */ scrollRight(params: IParams): void; + /** CSI B */ cursorDown(params: IParams): void; + /** CSI C */ cursorForward(params: IParams): void; + /** CSI D */ cursorBackward(params: IParams): void; + /** CSI E */ cursorNextLine(params: IParams): void; + /** CSI F */ cursorPrecedingLine(params: IParams): void; + /** CSI G */ cursorCharAbsolute(params: IParams): void; + /** CSI H */ cursorPosition(params: IParams): void; + /** CSI I */ cursorForwardTab(params: IParams): void; + /** CSI J */ eraseInDisplay(params: IParams): void; + /** CSI K */ eraseInLine(params: IParams): void; + /** CSI L */ insertLines(params: IParams): void; + /** CSI M */ deleteLines(params: IParams): void; + /** CSI P */ deleteChars(params: IParams): void; + /** CSI S */ scrollUp(params: IParams): void; + /** CSI T */ scrollDown(params: IParams, collect?: string): void; + /** CSI X */ eraseChars(params: IParams): void; + /** CSI Z */ cursorBackwardTab(params: IParams): void; + /** CSI ` */ charPosAbsolute(params: IParams): void; + /** CSI a */ hPositionRelative(params: IParams): void; + /** CSI b */ repeatPrecedingCharacter(params: IParams): void; + /** CSI c */ sendDeviceAttributesPrimary(params: IParams): void; + /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void; + /** CSI d */ linePosAbsolute(params: IParams): void; + /** CSI e */ vPositionRelative(params: IParams): void; + /** CSI f */ hVPosition(params: IParams): void; + /** CSI g */ tabClear(params: IParams): void; + /** CSI h */ setMode(params: IParams, collect?: string): void; + /** CSI l */ resetMode(params: IParams, collect?: string): void; + /** CSI m */ charAttributes(params: IParams): void; + /** CSI n */ deviceStatus(params: IParams, collect?: string): void; + /** CSI p */ softReset(params: IParams, collect?: string): void; + /** CSI q */ setCursorStyle(params: IParams, collect?: string): void; + /** CSI r */ setScrollRegion(params: IParams, collect?: string): void; + /** CSI s */ saveCursor(params: IParams): void; + /** CSI u */ restoreCursor(params: IParams): void; + /** CSI ' } */ insertColumns(params: IParams): void; + /** CSI ' ~ */ deleteColumns(params: IParams): void; + /** OSC 0 + OSC 2 */ setTitle(data: string): void; + /** ESC E */ nextLine(): void; + /** ESC = */ keypadApplicationMode(): void; + /** ESC > */ keypadNumericMode(): void; + /** ESC % G + ESC % @ */ selectDefaultCharset(): void; + /** ESC ( C + ESC ) C + ESC * C + ESC + C + ESC - C + ESC . C + ESC / C */ selectCharset(collectAndFlag: string): void; + /** ESC D */ index(): void; + /** ESC H */ tabSet(): void; + /** ESC M */ reverseIndex(): void; + /** ESC c */ fullReset(): void; + /** ESC n + ESC o + ESC | + ESC } + ESC ~ */ setgLevel(level: number): void; + /** ESC # 8 */ screenAlignmentPattern(): void; +} diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 152bab7a..1de39dbd 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -36,7 +36,7 @@ export class Buffer implements IBuffer { public savedY: number = 0; public savedX: number = 0; public savedCurAttrData = DEFAULT_ATTR_DATA.clone(); - public savedCharset: ICharset | null = DEFAULT_CHARSET; + public savedCharset: ICharset | undefined = DEFAULT_CHARSET; public markers: Marker[] = []; private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]); diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index e229d69c..c271f33c 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -31,7 +31,7 @@ export interface IBuffer { hasScrollback: boolean; savedY: number; savedX: number; - savedCharset: ICharset | null; + savedCharset: ICharset | undefined; savedCurAttrData: IAttributeData; isCursorInViewport: boolean; markers: IMarker[]; diff --git a/src/common/data/Charsets.ts b/src/common/data/Charsets.ts index 01087d8b..c72d5a23 100644 --- a/src/common/data/Charsets.ts +++ b/src/common/data/Charsets.ts @@ -10,12 +10,12 @@ import { ICharset } from 'common/Types'; * to be represented within the terminal with only 8-bit encoding. See ISO 2022 * for a discussion on character sets. Only VT100 character sets are supported. */ -export const CHARSETS: { [key: string]: ICharset | null } = {}; +export const CHARSETS: { [key: string]: ICharset | undefined } = {}; /** * The default character set, US. */ -export const DEFAULT_CHARSET: ICharset | null = CHARSETS['B']; +export const DEFAULT_CHARSET: ICharset | undefined = CHARSETS['B']; /** * DEC Special Character and Line Drawing Set. @@ -74,7 +74,7 @@ CHARSETS['A'] = { * United States character set * ESC (B */ -CHARSETS['B'] = null; +CHARSETS['B'] = undefined; /** * Dutch character set diff --git a/src/common/services/CharsetService.ts b/src/common/services/CharsetService.ts index 47a015f6..c5381065 100644 --- a/src/common/services/CharsetService.ts +++ b/src/common/services/CharsetService.ts @@ -10,22 +10,23 @@ export class CharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; - public charsets: ICharset[] = []; public glevel: number = 0; + private _charsets: (ICharset | undefined)[] = []; + public reset(): void { this.charset = undefined; - this.charsets = []; + this._charsets = []; this.glevel = 0; } public setgLevel(g: number): void { this.glevel = g; - this.charset = this.charsets[g]; + this.charset = this._charsets[g]; } - public setgCharset(g: number, charset: ICharset): void { - this.charsets[g] = charset; + public setgCharset(g: number, charset: ICharset | undefined): void { + this._charsets[g] = charset; if (this.glevel === g) { this.charset = charset; } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 8fa3f57b..6829d7b9 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -98,7 +98,6 @@ export interface ICharsetService { charset: ICharset | undefined; readonly glevel: number; - readonly charsets: ReadonlyArray; reset(): void; @@ -113,7 +112,7 @@ export interface ICharsetService { * @param g * @param charset */ - setgCharset(g: number, charset: ICharset): void; + setgCharset(g: number, charset: ICharset | undefined): void; } export const IDirtyRowService = createDecorator('DirtyRowService'); From 31670da63398969819bccb7ae7e2244ee5debfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Sun, 26 Apr 2020 16:53:15 +0200 Subject: [PATCH 21/35] fix sloppy coding in InputHandler --- src/InputHandler.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 3717256e..e80887cc 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -491,7 +491,7 @@ export class InputHandler extends Disposable implements IInputHandler { const wraparoundMode = this._coreService.decPrivateModes.wraparound; const insertMode = this._terminal.insertMode; const curAttr = this._curAttrData; - let bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + let bufferRow = buffer.lines.get(buffer.ybase + buffer.y); this._dirtyRowService.markDirty(buffer.y); @@ -555,10 +555,10 @@ export class InputHandler extends Disposable implements IInputHandler { } // The line already exists (eg. the initial viewport), mark it as a // wrapped line - buffer.lines.get(buffer.y).isWrapped = true; + buffer.lines.get(buffer.ybase + buffer.y).isWrapped = true; } // row changed, get it again - bufferRow = buffer.lines.get(buffer.y + buffer.ybase); + bufferRow = buffer.lines.get(buffer.ybase + buffer.y); } else { buffer.x = cols - 1; if (chWidth === 2) { @@ -1178,7 +1178,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } - const row: number = buffer.y + buffer.ybase; + const row: number = buffer.ybase + buffer.y; const scrollBottomRowsOffset = this._bufferService.rows - 1 - buffer.scrollBottom; const scrollBottomAbsolute = this._bufferService.rows - 1 + buffer.ybase - scrollBottomRowsOffset + 1; @@ -1213,7 +1213,7 @@ export class InputHandler extends Disposable implements IInputHandler { return; } - const row: number = buffer.y + buffer.ybase; + const row: number = buffer.ybase + buffer.y; let j: number; j = this._bufferService.rows - 1 - buffer.scrollBottom; @@ -1242,7 +1242,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public insertChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.insertCells( this._bufferService.buffer.x, @@ -1267,7 +1267,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public deleteChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.deleteCells( this._bufferService.buffer.x, @@ -1439,7 +1439,7 @@ export class InputHandler extends Disposable implements IInputHandler { */ public eraseChars(params: IParams): void { this._restrictCursor(); - const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.y + this._bufferService.buffer.ybase); + const line = this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase + this._bufferService.buffer.y); if (line) { line.replaceCells( this._bufferService.buffer.x, @@ -2712,8 +2712,8 @@ export class InputHandler extends Disposable implements IInputHandler { // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // blankLine(true) is xterm/linux behavior const scrollRegionHeight = buffer.scrollBottom - buffer.scrollTop; - buffer.lines.shiftElements(buffer.y + buffer.ybase, scrollRegionHeight, 1); - buffer.lines.set(buffer.y + buffer.ybase, buffer.getBlankLine(this._eraseAttrData())); + buffer.lines.shiftElements(buffer.ybase + buffer.y, scrollRegionHeight, 1); + buffer.lines.set(buffer.ybase + buffer.y, buffer.getBlankLine(this._eraseAttrData())); this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); } else { buffer.y--; @@ -2778,7 +2778,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) { - const row = buffer.y + buffer.ybase + yOffset; + const row = buffer.ybase + buffer.y + yOffset; buffer.lines.get(row).fill(cell); buffer.lines.get(row).isWrapped = false; } From c9313412594421303dd26d263478bac05e28ebb5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 08:08:49 -0700 Subject: [PATCH 22/35] Remove Terminal dep in InputHandler.test --- src/InputHandler.test.ts | 1561 +++++++++++++++++------------------- src/Terminal.test.ts | 84 ++ src/common/InputHandler.ts | 4 +- 3 files changed, 836 insertions(+), 813 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 5a3dc2b5..455fc754 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -5,8 +5,6 @@ import { assert, expect } from 'chai'; import { InputHandler } from './common/InputHandler'; -import { TestTerminal } from './TestUtils.test'; -import { Terminal } from './Terminal'; import { IBufferLine, IAttributeData } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; @@ -14,36 +12,52 @@ import { Attributes } 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 } from 'common/TestUtils.test'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, ICoreService } from 'common/services/Services'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; import { clone } from 'common/Clone'; +import { BufferService } from 'common/services/BufferService'; +import { CoreService } from 'common/services/CoreService'; -function getCursor(term: TestTerminal): number[] { +function getCursor(bufferService: IBufferService): number[] { return [ - term.buffer.x, - term.buffer.y + bufferService.buffer.x, + bufferService.buffer.y ]; } -function getLines(term: TestTerminal, limit: number = term.rows): string[] { +function getLines(bufferService: IBufferService, limit: number = bufferService.rows): string[] { const res: string[] = []; for (let i = 0; i < limit; ++i) { - res.push(term.buffer.lines.get(i).translateToString(true)); + res.push(bufferService.buffer.lines.get(i).translateToString(true)); } return res; } class TestInputHandler extends InputHandler { public get curAttrData(): IAttributeData { return (this as any)._curAttrData; } + public get windowTitleStack(): string[] { return this._windowTitleStack; } + public get iconNameStack(): string[] { return this._iconNameStack; } } describe('InputHandler', () => { - describe('save and restore cursor', () => { - const bufferService = new MockBufferService(80, 30); + let bufferService: IBufferService; + let coreService: ICoreService; + let optionsService: MockOptionsService; + let inputHandler: TestInputHandler; + + beforeEach(() => { + optionsService = new MockOptionsService(); + bufferService = new BufferService(optionsService); + bufferService.resize(80, 30); + coreService = new CoreService(() => {}, bufferService, new MockLogService(), optionsService); + + inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); + }); + + it('save and restore cursor', () => { bufferService.buffer.x = 1; bufferService.buffer.y = 2; bufferService.buffer.ybase = 0; - const inputHandler = new TestInputHandler(bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); inputHandler.curAttrData.fg = 3; // Save cursor position inputHandler.saveCursor(); @@ -62,9 +76,6 @@ describe('InputHandler', () => { }); describe('setCursorStyle', () => { it('should call Terminal.setOption with correct params', () => { - const optionsService = new MockOptionsService(); - const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); - inputHandler.setCursorStyle(Params.fromArray([0])); assert.equal(optionsService.options['cursorStyle'], 'block'); assert.equal(optionsService.options['cursorBlink'], true); @@ -341,23 +352,6 @@ describe('InputHandler', () => { expect(bufferService.buffer.lines.get(2).isWrapped).false; }); }); - it('convertEol setting', function(): void { - // not converting - const termNotConverting = new Terminal({cols: 15, rows: 10}); - (termNotConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); - expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); - - // converting - const termConverting = new Terminal({cols: 15, rows: 10, convertEol: true}); - (termConverting as any)._inputHandler.parse('Hello\nWorld'); - expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); - expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); - }); describe('print', () => { it('should not cause an infinite loop (regression test)', () => { const inputHandler = new InputHandler(new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService()); @@ -423,837 +417,772 @@ describe('InputHandler', () => { }); describe('text attributes', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal(); - }); it('bold', () => { - term.writeSync('\x1b[1m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isBold(), false); + inputHandler.parse('\x1b[1m'); + assert.equal(!!inputHandler.curAttrData.isBold(), true); + inputHandler.parse('\x1b[22m'); + assert.equal(!!inputHandler.curAttrData.isBold(), false); }); it('dim', () => { - term.writeSync('\x1b[2m'); - assert.equal(!!term.curAttrData.isDim(), true); - term.writeSync('\x1b[22m'); - assert.equal(!!term.curAttrData.isDim(), false); + inputHandler.parse('\x1b[2m'); + assert.equal(!!inputHandler.curAttrData.isDim(), true); + inputHandler.parse('\x1b[22m'); + assert.equal(!!inputHandler.curAttrData.isDim(), false); }); it('italic', () => { - term.writeSync('\x1b[3m'); - assert.equal(!!term.curAttrData.isItalic(), true); - term.writeSync('\x1b[23m'); - assert.equal(!!term.curAttrData.isItalic(), false); + inputHandler.parse('\x1b[3m'); + assert.equal(!!inputHandler.curAttrData.isItalic(), true); + inputHandler.parse('\x1b[23m'); + assert.equal(!!inputHandler.curAttrData.isItalic(), false); }); it('underline', () => { - term.writeSync('\x1b[4m'); - assert.equal(!!term.curAttrData.isUnderline(), true); - term.writeSync('\x1b[24m'); - assert.equal(!!term.curAttrData.isUnderline(), false); + inputHandler.parse('\x1b[4m'); + assert.equal(!!inputHandler.curAttrData.isUnderline(), true); + inputHandler.parse('\x1b[24m'); + assert.equal(!!inputHandler.curAttrData.isUnderline(), false); }); it('blink', () => { - term.writeSync('\x1b[5m'); - assert.equal(!!term.curAttrData.isBlink(), true); - term.writeSync('\x1b[25m'); - assert.equal(!!term.curAttrData.isBlink(), false); + inputHandler.parse('\x1b[5m'); + assert.equal(!!inputHandler.curAttrData.isBlink(), true); + inputHandler.parse('\x1b[25m'); + assert.equal(!!inputHandler.curAttrData.isBlink(), false); }); it('inverse', () => { - term.writeSync('\x1b[7m'); - assert.equal(!!term.curAttrData.isInverse(), true); - term.writeSync('\x1b[27m'); - assert.equal(!!term.curAttrData.isInverse(), false); + inputHandler.parse('\x1b[7m'); + assert.equal(!!inputHandler.curAttrData.isInverse(), true); + inputHandler.parse('\x1b[27m'); + assert.equal(!!inputHandler.curAttrData.isInverse(), false); }); it('invisible', () => { - term.writeSync('\x1b[8m'); - assert.equal(!!term.curAttrData.isInvisible(), true); - term.writeSync('\x1b[28m'); - assert.equal(!!term.curAttrData.isInvisible(), false); + inputHandler.parse('\x1b[8m'); + assert.equal(!!inputHandler.curAttrData.isInvisible(), true); + inputHandler.parse('\x1b[28m'); + assert.equal(!!inputHandler.curAttrData.isInvisible(), false); }); it('colormode palette 16', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 8; ++i) { - term.writeSync(`\x1b[${i + 30};${i + 40}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), i); + inputHandler.parse(`\x1b[${i + 30};${i + 40}m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), i); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), i); } // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getBgColorMode(), 0); + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); }); it('colormode palette 256', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT // lower 8 colors for (let i = 0; i < 256; ++i) { - term.writeSync(`\x1b[38;5;${i};48;5;${i}m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), i); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), i); + inputHandler.parse(`\x1b[38;5;${i};48;5;${i}m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), i); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), i); } // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getFgColor(), -1); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColor(), -1); }); it('colormode RGB', () => { - assert.equal(term.curAttrData.getFgColorMode(), 0); // DEFAULT - assert.equal(term.curAttrData.getBgColorMode(), 0); // DEFAULT - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_RGB); - assert.equal(term.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [1, 2, 3]); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_RGB); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getBgColor()), [4, 5, 6]); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); // DEFAULT + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); // DEFAULT + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_RGB); + assert.equal(inputHandler.curAttrData.getFgColor(), 1 << 16 | 2 << 8 | 3); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [1, 2, 3]); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_RGB); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getBgColor()), [4, 5, 6]); // reset to DEFAULT - term.writeSync(`\x1b[39;49m`); - assert.equal(term.curAttrData.getFgColorMode(), 0); - assert.equal(term.curAttrData.getFgColor(), -1); - assert.equal(term.curAttrData.getBgColorMode(), 0); - assert.equal(term.curAttrData.getBgColor(), -1); + inputHandler.parse(`\x1b[39;49m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getFgColor(), -1); + assert.equal(inputHandler.curAttrData.getBgColorMode(), 0); + assert.equal(inputHandler.curAttrData.getBgColor(), -1); }); it('colormode transition RGB to 256', () => { // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), 255); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), 255); }); it('colormode transition RGB to 16', () => { // enter RGB for FG and BG - term.writeSync(`\x1b[38;2;1;2;3;48;2;4;5;6m`); + inputHandler.parse(`\x1b[38;2;1;2;3;48;2;4;5;6m`); // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); + inputHandler.parse(`\x1b[37;47m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), 7); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), 7); }); it('colormode transition 16 to 256', () => { // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); + inputHandler.parse(`\x1b[37;47m`); // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getFgColor(), 255); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P256); - assert.equal(term.curAttrData.getBgColor(), 255); + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getFgColor(), 255); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P256); + assert.equal(inputHandler.curAttrData.getBgColor(), 255); }); it('colormode transition 256 to 16', () => { // enter 256 for FG and BG - term.writeSync(`\x1b[38;5;255;48;5;255m`); + inputHandler.parse(`\x1b[38;5;255;48;5;255m`); // enter 16 for FG and BG - term.writeSync(`\x1b[37;47m`); - assert.equal(term.curAttrData.getFgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getFgColor(), 7); - assert.equal(term.curAttrData.getBgColorMode(), Attributes.CM_P16); - assert.equal(term.curAttrData.getBgColor(), 7); + inputHandler.parse(`\x1b[37;47m`); + assert.equal(inputHandler.curAttrData.getFgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getFgColor(), 7); + assert.equal(inputHandler.curAttrData.getBgColorMode(), Attributes.CM_P16); + assert.equal(inputHandler.curAttrData.getBgColor(), 7); }); it('should zero missing RGB values', () => { - term.writeSync(`\x1b[38;2;1;2;3m`); - term.writeSync(`\x1b[38;2;5m`); - assert.deepEqual(AttributeData.toColorRGB(term.curAttrData.getFgColor()), [5, 0, 0]); + inputHandler.parse(`\x1b[38;2;1;2;3m`); + inputHandler.parse(`\x1b[38;2;5m`); + assert.deepEqual(AttributeData.toColorRGB(inputHandler.curAttrData.getFgColor()), [5, 0, 0]); }); }); describe('colon notation', () => { - let termColon: TestTerminal; - let termSemicolon: TestTerminal; + let inputHandler2: TestInputHandler; beforeEach(() => { - termColon = new TestTerminal(); - termSemicolon = new TestTerminal(); + inputHandler2 = new TestInputHandler(bufferService, new MockCharsetService(), coreService, new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService()); }); describe('should equal to semicolon', () => { it('CSI 38:2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38:2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38:2::50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:2::50:100: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;m'); - termColon.writeSync('\x1b[38:2::50:100:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;m'); + inputHandler.parse('\x1b[38:2::50:100:m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:2::50:: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;;m'); - termColon.writeSync('\x1b[38:2::50::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;;m'); + inputHandler.parse('\x1b[38:2::50::m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:2:::: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;;;m'); - termColon.writeSync('\x1b[38:2::::m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;;;m'); + inputHandler.parse('\x1b[38:2::::m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38;2::50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2::50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2::50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38;2;50:100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50:100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2;50:100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38;2;50;100:150 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2;50;100;150m'); - termColon.writeSync('\x1b[38;2;50;100:150m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2;50;100;150m'); + inputHandler.parse('\x1b[38;2;50;100:150m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38:5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;50m'); + inputHandler.parse('\x1b[38:5:50m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:5: m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;m'); - termColon.writeSync('\x1b[38:5:m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;m'); + inputHandler.parse('\x1b[38:5:m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38;5:50 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5;50m'); - termColon.writeSync('\x1b[38;5:50m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 50); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5;50m'); + inputHandler.parse('\x1b[38;5:50m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 50); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); }); describe('should fill early sequence end with default of 0', () => { it('CSI 38:2 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;2m'); - termColon.writeSync('\x1b[38:2m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;2m'); + inputHandler.parse('\x1b[38:2m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0 << 16 | 0 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 38:5 m', () => { - termColon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.curAttrData.fg = 0xFFFFFFFF; - termSemicolon.writeSync('\x1b[38;5m'); - termColon.writeSync('\x1b[38:5m'); - assert.equal(termSemicolon.curAttrData.fg & 0xFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.curAttrData.fg = 0xFFFFFFFF; + inputHandler2.parse('\x1b[38;5m'); + inputHandler.parse('\x1b[38:5m'); + assert.equal(inputHandler2.curAttrData.fg & 0xFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); }); describe('should not interfere with leading/following SGR attrs', () => { it('CSI 1 ; 38:2::50:100:150 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;150;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:150;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler2.parse('\x1b[1;38;2;50;100;150;4m'); + inputHandler.parse('\x1b[1;38:2::50:100:150;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 150); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 1 ; 38:2::50:100: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100:;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); + inputHandler.parse('\x1b[1;38:2::50:100:;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 1 ; 38:2::50:100 ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;50;100;;4m'); - termColon.writeSync('\x1b[1;38:2::50:100;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler2.parse('\x1b[1;38;2;50;100;;4m'); + inputHandler.parse('\x1b[1;38:2::50:100;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 50 << 16 | 100 << 8 | 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 1 ; 38:2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38:2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler2.parse('\x1b[1;38;2;;;;4m'); + inputHandler.parse('\x1b[1;38:2::;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); it('CSI 1 ; 38;2:: ; 4 m', () => { - termSemicolon.writeSync('\x1b[1;38;2;;;;4m'); - termColon.writeSync('\x1b[1;38;2::;4m'); - assert.equal(!!termSemicolon.curAttrData.isBold(), true); - assert.equal(!!termSemicolon.curAttrData.isUnderline(), true); - assert.equal(termSemicolon.curAttrData.fg & 0xFFFFFF, 0); - assert.equal(termColon.curAttrData.fg, termSemicolon.curAttrData.fg); + inputHandler2.parse('\x1b[1;38;2;;;;4m'); + inputHandler.parse('\x1b[1;38;2::;4m'); + assert.equal(!!inputHandler2.curAttrData.isBold(), true); + assert.equal(!!inputHandler2.curAttrData.isUnderline(), true); + assert.equal(inputHandler2.curAttrData.fg & 0xFFFFFF, 0); + assert.equal(inputHandler.curAttrData.fg, inputHandler2.curAttrData.fg); }); }); }); describe('cursor positioning', () => { - let term: TestTerminal; beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); + bufferService.resize(10, 10); }); it('cursor forward (CUF)', () => { - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1C'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4C'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100C'); - assert.deepEqual(getCursor(term), [9, 0]); + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[1C'); + assert.deepEqual(getCursor(bufferService), [2, 0]); + inputHandler.parse('\x1b[4C'); + assert.deepEqual(getCursor(bufferService), [6, 0]); + inputHandler.parse('\x1b[100C'); + assert.deepEqual(getCursor(bufferService), [9, 0]); // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 4]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [9, 4]); }); it('cursor backward (CUB)', () => { - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at end of first line - term.writeSync('\x1b[100C'); - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 0]); - term.writeSync('\x1b[1D'); - assert.deepEqual(getCursor(term), [7, 0]); - term.writeSync('\x1b[4D'); - assert.deepEqual(getCursor(term), [3, 0]); - term.writeSync('\x1b[100D'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[100C'); + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [8, 0]); + inputHandler.parse('\x1b[1D'); + assert.deepEqual(getCursor(bufferService), [7, 0]); + inputHandler.parse('\x1b[4D'); + assert.deepEqual(getCursor(bufferService), [3, 0]); + inputHandler.parse('\x1b[100D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // should not change y - term.buffer.x = 4; - term.buffer.y = 4; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [3, 4]); + bufferService.buffer.x = 4; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [3, 4]); }); it('cursor down (CUD)', () => { - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1B'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4B'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100B'); - assert.deepEqual(getCursor(term), [0, 9]); + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1B'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4B'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100B'); + assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [8, 1]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 0; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [8, 1]); }); it('cursor up (CUU)', () => { - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at beginning of last row - term.writeSync('\x1b[100B'); - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1A'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4A'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100A'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[100B'); + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + inputHandler.parse('\x1b[1A'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + inputHandler.parse('\x1b[4A'); + assert.deepEqual(getCursor(bufferService), [0, 3]); + inputHandler.parse('\x1b[100A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // should not change x - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [8, 8]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 9; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [8, 8]); }); it('cursor next line (CNL)', () => { - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1E'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4E'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100E'); - assert.deepEqual(getCursor(term), [0, 9]); + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1E'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4E'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100E'); + assert.deepEqual(getCursor(bufferService), [0, 9]); // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 0; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 0; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); }); it('cursor previous line (CPL)', () => { - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // place cursor at beginning of last row - term.writeSync('\x1b[100E'); - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.writeSync('\x1b[1F'); - assert.deepEqual(getCursor(term), [0, 7]); - term.writeSync('\x1b[4F'); - assert.deepEqual(getCursor(term), [0, 3]); - term.writeSync('\x1b[100F'); - assert.deepEqual(getCursor(term), [0, 0]); + inputHandler.parse('\x1b[100E'); + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + inputHandler.parse('\x1b[1F'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + inputHandler.parse('\x1b[4F'); + assert.deepEqual(getCursor(bufferService), [0, 3]); + inputHandler.parse('\x1b[100F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); // should reset x to zero - term.buffer.x = 8; - term.buffer.y = 9; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 9; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); }); it('cursor character absolute (CHA)', () => { - term.writeSync('\x1b[G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1G'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2G'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100G'); - assert.deepEqual(getCursor(term), [9, 0]); + inputHandler.parse('\x1b[G'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1G'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2G'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + inputHandler.parse('\x1b[100G'); + assert.deepEqual(getCursor(bufferService), [9, 0]); }); it('cursor position (CUP)', () => { - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[1;1H'); - assert.deepEqual(getCursor(term), [0, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[8H'); - assert.deepEqual(getCursor(term), [0, 7]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[;8H'); - assert.deepEqual(getCursor(term), [7, 0]); - term.buffer.x = 5; - term.buffer.y = 5; - term.writeSync('\x1b[100;100H'); - assert.deepEqual(getCursor(term), [9, 9]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[1H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[1;1H'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[8H'); + assert.deepEqual(getCursor(bufferService), [0, 7]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[;8H'); + assert.deepEqual(getCursor(bufferService), [7, 0]); + bufferService.buffer.x = 5; + bufferService.buffer.y = 5; + inputHandler.parse('\x1b[100;100H'); + assert.deepEqual(getCursor(bufferService), [9, 9]); }); it('horizontal position absolute (HPA)', () => { - term.writeSync('\x1b[`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1`'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2`'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); - term.writeSync('\x1b[100`'); - assert.deepEqual(getCursor(term), [9, 0]); + inputHandler.parse('\x1b[`'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1`'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2`'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 0]); + inputHandler.parse('\x1b[100`'); + assert.deepEqual(getCursor(bufferService), [9, 0]); }); it('horizontal position relative (HPR)', () => { - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); - term.writeSync('\x1b[1a'); - assert.deepEqual(getCursor(term), [2, 0]); - term.writeSync('\x1b[4a'); - assert.deepEqual(getCursor(term), [6, 0]); - term.writeSync('\x1b[100a'); - assert.deepEqual(getCursor(term), [9, 0]); + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [1, 0]); + inputHandler.parse('\x1b[1a'); + assert.deepEqual(getCursor(bufferService), [2, 0]); + inputHandler.parse('\x1b[4a'); + assert.deepEqual(getCursor(bufferService), [6, 0]); + inputHandler.parse('\x1b[100a'); + assert.deepEqual(getCursor(bufferService), [9, 0]); // should not change y - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 4]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [9, 4]); }); it('vertical position absolute (VPA)', () => { - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[1d'); - assert.deepEqual(getCursor(term), [0, 0]); - term.writeSync('\x1b[2d'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); - term.writeSync('\x1b[100d'); - assert.deepEqual(getCursor(term), [0, 9]); + inputHandler.parse('\x1b[d'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[1d'); + assert.deepEqual(getCursor(bufferService), [0, 0]); + inputHandler.parse('\x1b[2d'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [0, 4]); + inputHandler.parse('\x1b[100d'); + assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[d'); - assert.deepEqual(getCursor(term), [8, 0]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[d'); + assert.deepEqual(getCursor(bufferService), [8, 0]); }); it('vertical position relative (VPR)', () => { - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); - term.writeSync('\x1b[1e'); - assert.deepEqual(getCursor(term), [0, 2]); - term.writeSync('\x1b[4e'); - assert.deepEqual(getCursor(term), [0, 6]); - term.writeSync('\x1b[100e'); - assert.deepEqual(getCursor(term), [0, 9]); + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [0, 1]); + inputHandler.parse('\x1b[1e'); + assert.deepEqual(getCursor(bufferService), [0, 2]); + inputHandler.parse('\x1b[4e'); + assert.deepEqual(getCursor(bufferService), [0, 6]); + inputHandler.parse('\x1b[100e'); + assert.deepEqual(getCursor(bufferService), [0, 9]); // should not change x - term.buffer.x = 8; - term.buffer.y = 4; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [8, 5]); + bufferService.buffer.x = 8; + bufferService.buffer.y = 4; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [8, 5]); }); describe('should clamp cursor into addressible range', () => { it('CUF', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[C'); - assert.deepEqual(getCursor(term), [1, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[C'); + assert.deepEqual(getCursor(bufferService), [1, 0]); }); it('CUB', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [8, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[D'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [8, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[D'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('CUD', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[B'); - assert.deepEqual(getCursor(term), [0, 1]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[B'); + assert.deepEqual(getCursor(bufferService), [0, 1]); }); it('CUU', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [9, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[A'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [9, 8]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[A'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('CNL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[E'); - assert.deepEqual(getCursor(term), [0, 1]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[E'); + assert.deepEqual(getCursor(bufferService), [0, 1]); }); it('CPL', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 8]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[F'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 8]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[F'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('CHA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5G'); - assert.deepEqual(getCursor(term), [4, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5G'); + assert.deepEqual(getCursor(bufferService), [4, 0]); }); it('CUP', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5;5H'); - assert.deepEqual(getCursor(term), [4, 4]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5;5H'); + assert.deepEqual(getCursor(bufferService), [4, 4]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5;5H'); + assert.deepEqual(getCursor(bufferService), [4, 4]); }); it('HPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5`'); - assert.deepEqual(getCursor(term), [4, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5`'); + assert.deepEqual(getCursor(bufferService), [4, 0]); }); it('HPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[a'); - assert.deepEqual(getCursor(term), [1, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[a'); + assert.deepEqual(getCursor(bufferService), [1, 0]); }); it('VPA', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [9, 4]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[5d'); - assert.deepEqual(getCursor(term), [0, 4]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [9, 4]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[5d'); + assert.deepEqual(getCursor(bufferService), [0, 4]); }); it('VPR', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[e'); - assert.deepEqual(getCursor(term), [0, 1]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[e'); + assert.deepEqual(getCursor(bufferService), [0, 1]); }); it('DCH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[P'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[P'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[P'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('DCH - should delete last cell', () => { - term.writeSync('0123456789\x1b[P'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); + inputHandler.parse('0123456789\x1b[P'); + assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); }); it('ECH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[X'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[X'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[X'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('ECH - should delete last cell', () => { - term.writeSync('0123456789\x1b[X'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); + inputHandler.parse('0123456789\x1b[X'); + assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); }); it('ICH', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [9, 9]); - term.buffer.x = -10000; - term.buffer.y = -10000; - term.writeSync('\x1b[@'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[@'); + assert.deepEqual(getCursor(bufferService), [9, 9]); + bufferService.buffer.x = -10000; + bufferService.buffer.y = -10000; + inputHandler.parse('\x1b[@'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); it('ICH - should delete last cell', () => { - term.writeSync('0123456789\x1b[@'); - assert.equal(term.buffer.lines.get(0).translateToString(false), '012345678 '); + inputHandler.parse('0123456789\x1b[@'); + assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); }); }); }); describe('DECSTBM - scroll margins', () => { - let term: TestTerminal; beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); + bufferService.resize(10, 10); }); it('should default to whole viewport', () => { - term.writeSync('\x1b[r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); - term.writeSync('\x1b[3;7r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 6); - term.writeSync('\x1b[0;0r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); + inputHandler.parse('\x1b[r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); + inputHandler.parse('\x1b[3;7r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 6); + inputHandler.parse('\x1b[0;0r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); }); it('should clamp bottom', () => { - term.writeSync('\x1b[3;1000r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 9); + inputHandler.parse('\x1b[3;1000r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 9); }); it('should only apply for top < bottom', () => { - term.writeSync('\x1b[7;2r'); - assert.equal(term.buffer.scrollTop, 0); - assert.equal(term.buffer.scrollBottom, 9); + inputHandler.parse('\x1b[7;2r'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, 9); }); it('should home cursor', () => { - term.buffer.x = 10000; - term.buffer.y = 10000; - term.writeSync('\x1b[2;7r'); - assert.deepEqual(getCursor(term), [0, 0]); + bufferService.buffer.x = 10000; + bufferService.buffer.y = 10000; + inputHandler.parse('\x1b[2;7r'); + assert.deepEqual(getCursor(bufferService), [0, 0]); }); }); describe('scroll margins', () => { - let term: TestTerminal; beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 10}); + bufferService.resize(10, 10); }); it('scrollUp', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); - assert.deepEqual(getLines(term), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Sm'); + assert.deepEqual(getLines(bufferService), ['m', '3', '', '', '4', '5', '6', '7', '8', '9']); }); it('scrollDown', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); - assert.deepEqual(getLines(term), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[2;4r\x1b[2Tm'); + assert.deepEqual(getLines(bufferService), ['m', '', '', '1', '4', '5', '6', '7', '8', '9']); }); it('insertLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Lm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[2Lm'); + assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('\x1b[2H\x1b[2Ln'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Lo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Lp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Lq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); + inputHandler.parse('\x1b[7H\x1b[2Lo'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); + inputHandler.parse('\x1b[8H\x1b[2Lp'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); + inputHandler.parse('\x1b[100H\x1b[2Lq'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); }); it('insertLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[3H\x1b[2Lm'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); - term.writeSync('\x1b[6H\x1b[2Ln'); - assert.deepEqual(getLines(term), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[3H\x1b[2Lm'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', '3', '6', '7', '8', '9']); + inputHandler.parse('\x1b[6H\x1b[2Ln'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'm', '', '2', 'n', '6', '7', '8', '9']); }); it('deleteLines - out of margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[2Mm'); - assert.deepEqual(getLines(term), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); - term.writeSync('\x1b[2H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[2Mm'); + assert.deepEqual(getLines(bufferService), ['m', '1', '2', '3', '4', '5', '6', '7', '8', '9']); + inputHandler.parse('\x1b[2H\x1b[2Mn'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', '6', '7', '8', '9']); // skip below scrollbottom - term.writeSync('\x1b[7H\x1b[2Mo'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); - term.writeSync('\x1b[8H\x1b[2Mp'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); - term.writeSync('\x1b[100H\x1b[2Mq'); - assert.deepEqual(getLines(term), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); + inputHandler.parse('\x1b[7H\x1b[2Mo'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', '7', '8', '9']); + inputHandler.parse('\x1b[8H\x1b[2Mp'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', '9']); + inputHandler.parse('\x1b[100H\x1b[2Mq'); + assert.deepEqual(getLines(bufferService), ['m', 'n', '2', '3', '4', '5', 'o', 'p', '8', 'q']); }); it('deleteLines - within margins', () => { - term.writeSync('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); - assert.equal(term.buffer.scrollTop, 2); - assert.equal(term.buffer.scrollBottom, 5); - term.writeSync('\x1b[6H\x1b[2Mm'); - assert.deepEqual(getLines(term), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); - term.writeSync('\x1b[3H\x1b[2Mn'); - assert.deepEqual(getLines(term), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); - }); - }); - describe('SL/SR/DECIC/DECDC', () => { - let term: TestTerminal; - beforeEach(() => { - term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); - }); - it('SL (scrollLeft)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']); - term.writeSync('\x1b[0 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']); - term.writeSync('\x1b[2 @'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']); - }); - it('SR (scrollRight)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[ A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); - term.writeSync('\x1b[0 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); - term.writeSync('\x1b[2 A'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); - }); - it('insertColumns (DECIC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'}'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); - }); - it('deleteColumns (DECDC)', () => { - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[1\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); - term.reset(); - term.writeSync('12345'.repeat(6)); - term.writeSync('\x1b[3;3H'); - term.writeSync('\x1b[2\'~'); - assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); + inputHandler.parse('0\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7\r\n8\r\n9\x1b[3;6r'); + assert.equal(bufferService.buffer.scrollTop, 2); + assert.equal(bufferService.buffer.scrollBottom, 5); + inputHandler.parse('\x1b[6H\x1b[2Mm'); + assert.deepEqual(getLines(bufferService), ['0', '1', '2', '3', '4', 'm', '6', '7', '8', '9']); + inputHandler.parse('\x1b[3H\x1b[2Mn'); + assert.deepEqual(getLines(bufferService), ['0', '1', 'n', 'm', '', '', '6', '7', '8', '9']); }); }); it('should parse big chunks in smaller subchunks', () => { // max single chunk size is hardcoded as 131072 const calls: any[] = []; - const term = new TestTerminal({cols: 10, rows: 10}); - (term as any)._inputHandler._parser.parse = (data: Uint32Array, length: number) => { + bufferService.resize(10, 10); + (inputHandler as any)._parser.parse = (data: Uint32Array, length: number) => { calls.push([data.length, length]); }; - term.writeSync('12345'); - term.writeSync('a'.repeat(10000)); - term.writeSync('a'.repeat(200000)); - term.writeSync('a'.repeat(300000)); + inputHandler.parse('12345'); + inputHandler.parse('a'.repeat(10000)); + inputHandler.parse('a'.repeat(200000)); + inputHandler.parse('a'.repeat(300000)); assert.deepEqual(calls, [ [4096, 5], [10000, 10000], @@ -1263,226 +1192,236 @@ describe('InputHandler', () => { }); describe('windowOptions', () => { it('all should be disabled by default and not report', () => { - const term = new TestTerminal({cols: 10, rows: 10}); + bufferService.resize(10, 10); const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); - term.writeSync('\x1b[16t'); - term.writeSync('\x1b[18t'); - term.writeSync('\x1b[20t'); - term.writeSync('\x1b[21t'); + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[14t'); + inputHandler.parse('\x1b[16t'); + inputHandler.parse('\x1b[18t'); + inputHandler.parse('\x1b[20t'); + inputHandler.parse('\x1b[21t'); assert.deepEqual(stack, []); }); it('14 - GetWinSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.getWinSizePixels = true; const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[14t'); + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[14t'); // does not report in test terminal due to missing renderer assert.deepEqual(stack, []); }); it('16 - GetCellSizePixels', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.getCellSizePixels = true; const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[16t'); + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[16t'); // does not report in test terminal due to missing renderer assert.deepEqual(stack, []); }); it('18 - GetWinSizeChars', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.getWinSizeChars = true; const stack: string[] = []; - term.onData(data => stack.push(data)); - term.writeSync('\x1b[18t'); + coreService.onData(data => stack.push(data)); + inputHandler.parse('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t']); - term.resize(50, 20); - term.writeSync('\x1b[18t'); + bufferService.resize(50, 20); + inputHandler.parse('\x1b[18t'); assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']); }); it('22/23 - PushTitle/PopTitle', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22t'); + assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); + assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); - term.writeSync('\x1b[23t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); + inputHandler.parse('\x1b[23t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('22/23 - PushTitle/PopTitle with ;1', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;1t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;1t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']); + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22;1t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22;1t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22;1t'); + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, ['1', '2', '3']); assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); - term.writeSync('\x1b[23;1t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); + inputHandler.parse('\x1b[23;1t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3']); }); it('22/23 - PushTitle/PopTitle with ;2', () => { - const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}}); + bufferService.resize(10, 10); + optionsService.options.windowOptions.pushTitle = true; + optionsService.options.windowOptions.popTitle = true; const stack: string[] = []; - term.onTitleChange(data => stack.push(data)); - term.writeSync('\x1b]0;1\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;2\x07'); - term.writeSync('\x1b[22;2t'); - term.writeSync('\x1b]0;3\x07'); - term.writeSync('\x1b[22;2t'); - assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + inputHandler.onTitleChange(data => stack.push(data)); + inputHandler.parse('\x1b]0;1\x07'); + inputHandler.parse('\x1b[22;2t'); + inputHandler.parse('\x1b]0;2\x07'); + inputHandler.parse('\x1b[22;2t'); + inputHandler.parse('\x1b]0;3\x07'); + inputHandler.parse('\x1b[22;2t'); + assert.deepEqual(inputHandler.windowTitleStack, ['1', '2', '3']); + assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3']); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); - term.writeSync('\x1b[23;2t'); // one more to test "overflow" - assert.deepEqual((term as any)._inputHandler._windowTitleStack, []); - assert.deepEqual((term as any)._inputHandler._iconNameStack, []); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); + inputHandler.parse('\x1b[23;2t'); // one more to test "overflow" + assert.deepEqual(inputHandler.windowTitleStack, []); + assert.deepEqual(inputHandler.iconNameStack, []); assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']); }); it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => { // disabled - const term = new TestTerminal({cols: 10, rows: 10}); - term.writeSync('\x1b[?3l'); - assert.equal((term as any)._bufferService.cols, 10); - term.writeSync('\x1b[?3h'); - assert.equal((term as any)._bufferService.cols, 10); + bufferService.resize(10, 10); + inputHandler.parse('\x1b[?3l'); + assert.equal(bufferService.cols, 10); + inputHandler.parse('\x1b[?3h'); + assert.equal(bufferService.cols, 10); // enabled - const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}}); - term2.writeSync('\x1b[?3l'); - assert.equal((term2 as any)._bufferService.cols, 80); - term2.writeSync('\x1b[?3h'); - assert.equal((term2 as any)._bufferService.cols, 132); + inputHandler.reset(); + optionsService.options.windowOptions.setWinLines = true; + inputHandler.parse('\x1b[?3l'); + assert.equal(bufferService.cols, 80); + inputHandler.parse('\x1b[?3h'); + assert.equal(bufferService.cols, 132); }); }); describe('should correctly reset cells taken by wide chars', () => { - let term: TestTerminal; beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); + bufferService.resize(10, 5); + optionsService.options.scrollback = 1; + inputHandler.parse('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥'); }); it('print', () => { - term.writeSync('\x1b[H#'); - assert.deepEqual(getLines(term), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[1;6H######'); - assert.deepEqual(getLines(term), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;9H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); - term.writeSync('#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); - term.writeSync('\x1b[4;10H#'); - assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); + inputHandler.parse('\x1b[H#'); + assert.deepEqual(getLines(bufferService), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[1;6H######'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;9H#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']); + inputHandler.parse('#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']); + inputHandler.parse('\x1b[4;10H#'); + assert.deepEqual(getLines(bufferService), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']); }); it('EL', () => { - term.writeSync('\x1b[1;6H\x1b[K#'); - assert.deepEqual(getLines(term), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;5H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[1K'); - assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[1;6H\x1b[K#'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;5H\x1b[1K'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[1K'); + assert.deepEqual(getLines(bufferService), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']); }); it('ICH', () => { - term.writeSync('\x1b[1;6H\x1b[@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;4H\x1b[2@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;4H\x1b[3@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[4;4H\x1b[4@'); - assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); + inputHandler.parse('\x1b[1;6H\x1b[@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;4H\x1b[2@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;4H\x1b[3@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[4;4H\x1b[4@'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']); }); it('DCH', () => { - term.writeSync('\x1b[1;6H\x1b[P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3P'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[1;6H\x1b[P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;6H\x1b[2P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[3P'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); it('ECH', () => { - term.writeSync('\x1b[1;6H\x1b[X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[2;6H\x1b[2X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); - term.writeSync('\x1b[3;6H\x1b[3X'); - assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[1;6H\x1b[X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[2;6H\x1b[2X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']); + inputHandler.parse('\x1b[3;6H\x1b[3X'); + assert.deepEqual(getLines(bufferService), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']); }); }); describe('DECSTR', () => { - let term: TestTerminal; beforeEach(() => { - term = new TestTerminal({cols: 10, rows: 5, scrollback: 1}); - term.writeSync('01234567890123'); + bufferService.resize(10, 5); + optionsService.options.scrollback = 1; + inputHandler.parse('01234567890123'); }); it('should reset IRM', () => { - term.writeSync('\x1b[4h'); - assert.equal((term as any)._coreService.modes.insertMode, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.modes.insertMode, false); + inputHandler.parse('\x1b[4h'); + assert.equal(coreService.modes.insertMode, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.modes.insertMode, false); }); it('should reset cursor visibility', () => { - term.writeSync('\x1b[?25l'); - assert.equal((term as any)._coreService.isCursorHidden, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.isCursorHidden, false); + inputHandler.parse('\x1b[?25l'); + assert.equal(coreService.isCursorHidden, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.isCursorHidden, false); }); it('should reset scroll margins', () => { - term.writeSync('\x1b[2;4r'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 1); - assert.equal((term as any)._bufferService.buffer.scrollBottom, 3); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.scrollTop, 0); - assert.equal((term as any)._bufferService.buffer.scrollBottom, term.rows - 1); + inputHandler.parse('\x1b[2;4r'); + assert.equal(bufferService.buffer.scrollTop, 1); + assert.equal(bufferService.buffer.scrollBottom, 3); + inputHandler.parse('\x1b[!p'); + assert.equal(bufferService.buffer.scrollTop, 0); + assert.equal(bufferService.buffer.scrollBottom, bufferService.rows - 1); }); it('should reset text attributes', () => { - term.writeSync('\x1b[1;2;32;43m'); - assert.equal(!!term.curAttrData.isBold(), true); - term.writeSync('\x1b[!p'); - assert.equal(!!term.curAttrData.isBold(), false); - assert.equal(term.curAttrData.fg, 0); - assert.equal(term.curAttrData.bg, 0); + inputHandler.parse('\x1b[1;2;32;43m'); + assert.equal(!!inputHandler.curAttrData.isBold(), true); + inputHandler.parse('\x1b[!p'); + assert.equal(!!inputHandler.curAttrData.isBold(), false); + assert.equal(inputHandler.curAttrData.fg, 0); + assert.equal(inputHandler.curAttrData.bg, 0); }); it('should reset DECSC data', () => { - term.writeSync('\x1b7'); - assert.equal((term as any)._bufferService.buffer.savedX, 4); - assert.equal((term as any)._bufferService.buffer.savedY, 1); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._bufferService.buffer.savedX, 0); - assert.equal((term as any)._bufferService.buffer.savedY, 0); + inputHandler.parse('\x1b7'); + assert.equal(bufferService.buffer.savedX, 4); + assert.equal(bufferService.buffer.savedY, 1); + inputHandler.parse('\x1b[!p'); + assert.equal(bufferService.buffer.savedX, 0); + assert.equal(bufferService.buffer.savedY, 0); }); it('should reset DECOM', () => { - term.writeSync('\x1b[?6h'); - assert.equal((term as any)._coreService.decPrivateModes.origin, true); - term.writeSync('\x1b[!p'); - assert.equal((term as any)._coreService.decPrivateModes.origin, false); + inputHandler.parse('\x1b[?6h'); + assert.equal(coreService.decPrivateModes.origin, true); + inputHandler.parse('\x1b[!p'); + assert.equal(coreService.decPrivateModes.origin, false); }); }); }); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 61b40d6c..6488aa43 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -1401,6 +1401,90 @@ describe('Terminal', () => { assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); }); }); + it('convertEol setting', function(): void { + // not converting + const termNotConverting = new TestTerminal({cols: 15, rows: 10}); + termNotConverting.writeSync('Hello\nWorld'); + expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); + expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); + + // converting + const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true}); + termConverting.writeSync('Hello\nWorld'); + expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); + expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); + expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); + expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); + }); + describe('Terminal InputHandler integration', () => { + function getLines(term: TestTerminal, limit: number = term.rows): string[] { + const res: string[] = []; + for (let i = 0; i < limit; ++i) { + res.push(term.buffer.lines.get(i).translateToString(true)); + } + return res; + } + + // This suite cannot live in InputHandler unless Terminal.scroll moved into IBufferService + describe('SL/SR/DECIC/DECDC', () => { + let term: TestTerminal; + beforeEach(() => { + term = new TestTerminal({cols: 5, rows: 5, scrollback: 1}); + }); + it('SL (scrollLeft)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[ @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '2345', '2345', '2345', '2345', '2345']); + term.writeSync('\x1b[0 @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '345', '345', '345', '345', '345']); + term.writeSync('\x1b[2 @'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '5', '5', '5', '5', '5']); + }); + it('SR (scrollRight)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[ A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']); + term.writeSync('\x1b[0 A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 123', ' 123', ' 123', ' 123', ' 123']); + term.writeSync('\x1b[2 A'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']); + }); + it('insertColumns (DECIC)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[1\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 34', '12 34', '12 34', '12 34', '12 34']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[2\'}'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']); + }); + it('deleteColumns (DECDC)', () => { + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[1\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '1245', '1245', '1245', '1245', '1245']); + term.reset(); + term.writeSync('12345'.repeat(6)); + term.writeSync('\x1b[3;3H'); + term.writeSync('\x1b[2\'~'); + assert.deepEqual(getLines(term, term.rows + 1), ['12345', '125', '125', '125', '125', '125']); + }); + }); + }); }); class TestLinkifier extends Linkifier { diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 70ec8859..6a7460a4 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -219,8 +219,8 @@ export class InputHandler extends Disposable implements IInputHandler { private _workCell: CellData = new CellData(); private _windowTitle = ''; private _iconName = ''; - private _windowTitleStack: string[] = []; - private _iconNameStack: string[] = []; + protected _windowTitleStack: string[] = []; + protected _iconNameStack: string[] = []; private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone(); private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone(); From 212061be502858d21a5d2920f66713c7f418600a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 08:11:53 -0700 Subject: [PATCH 23/35] Move InputHandler.test into common, fix strict errors --- src/{ => common}/InputHandler.test.ts | 46 +++++++++++++-------------- 1 file changed, 23 insertions(+), 23 deletions(-) rename src/{ => common}/InputHandler.test.ts (97%) diff --git a/src/InputHandler.test.ts b/src/common/InputHandler.test.ts similarity index 97% rename from src/InputHandler.test.ts rename to src/common/InputHandler.test.ts index 455fc754..8f3ff9af 100644 --- a/src/InputHandler.test.ts +++ b/src/common/InputHandler.test.ts @@ -4,7 +4,7 @@ */ import { assert, expect } from 'chai'; -import { InputHandler } from './common/InputHandler'; +import { InputHandler } from 'common/InputHandler'; import { IBufferLine, IAttributeData } from 'common/Types'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; @@ -28,7 +28,7 @@ function getCursor(bufferService: IBufferService): number[] { function getLines(bufferService: IBufferService, limit: number = bufferService.rows): string[] { const res: string[] = []; for (let i = 0; i < limit; ++i) { - res.push(bufferService.buffer.lines.get(i).translateToString(true)); + res.push(bufferService.buffer.lines.get(i)!.translateToString(true)); } return res; } @@ -126,7 +126,7 @@ describe('InputHandler', () => { describe('regression tests', function(): void { function termContent(bufferService: IBufferService, trim: boolean): string[] { const result = []; - for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i).translateToString(trim)); + for (let i = 0; i < bufferService.rows; ++i) result.push(bufferService.buffer.lines.get(i)!.translateToString(trim)); return result; } @@ -139,7 +139,7 @@ describe('InputHandler', () => { inputHandler.parse('1234567890'); inputHandler.parse(Array(bufferService.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); + const line1: IBufferLine = bufferService.buffer.lines.get(0)!; expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); // insert one char from params = [0] @@ -176,7 +176,7 @@ describe('InputHandler', () => { inputHandler.parse('1234567890'); inputHandler.parse(Array(bufferService.cols - 9).join('a')); inputHandler.parse('1234567890'); - const line1: IBufferLine = bufferService.buffer.lines.get(0); + const line1: IBufferLine = bufferService.buffer.lines.get(0)!; expect(line1.translateToString(false)).equals(Array(bufferService.cols - 9).join('a') + '1234567890'); // delete one char from params = [0] @@ -220,19 +220,19 @@ describe('InputHandler', () => { bufferService.buffer.y = 0; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([0])); - expect(bufferService.buffer.lines.get(0).translateToString(false)).equals(Array(71).join('a') + ' '); + expect(bufferService.buffer.lines.get(0)!.translateToString(false)).equals(Array(71).join('a') + ' '); // params[1] - left erase bufferService.buffer.y = 1; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(1).translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); + expect(bufferService.buffer.lines.get(1)!.translateToString(false)).equals(Array(71).join(' ') + ' aaaaaaaaa'); // params[1] - left erase bufferService.buffer.y = 2; bufferService.buffer.x = 70; inputHandler.eraseInLine(Params.fromArray([2])); - expect(bufferService.buffer.lines.get(2).translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); + expect(bufferService.buffer.lines.get(2)!.translateToString(false)).equals(Array(bufferService.cols + 1).join(' ')); }); it('eraseInDisplay', function(): void { @@ -330,11 +330,11 @@ describe('InputHandler', () => { // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; + expect(bufferService.buffer.lines.get(2)!.isWrapped).true; bufferService.buffer.y = 2; bufferService.buffer.x = 40; inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; + expect(bufferService.buffer.lines.get(2)!.isWrapped).false; // reset and add a wrapped line bufferService.buffer.y = 0; @@ -345,11 +345,11 @@ describe('InputHandler', () => { // params[1] left and above with wrap // confirm precondition that line 2 is wrapped - expect(bufferService.buffer.lines.get(2).isWrapped).true; + expect(bufferService.buffer.lines.get(2)!.isWrapped).true; bufferService.buffer.y = 1; bufferService.buffer.x = 90; // Cursor is beyond last column inputHandler.eraseInDisplay(Params.fromArray([1])); - expect(bufferService.buffer.lines.get(2).isWrapped).false; + expect(bufferService.buffer.lines.get(2)!.isWrapped).false; }); }); describe('print', () => { @@ -374,45 +374,45 @@ describe('InputHandler', () => { expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); + expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1047 (alt screen buffer)', () => { handler.parse('\x1b[?1047h\r\n\x1b[31mJUNK\x1b[?1047lTEST'); expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal(''); expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(' TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(4, new CellData()).getFgColor())).to.equal(1); + expect((bufferService.buffer.lines.get(1)!.loadCell(4, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1048 (alt screen cursor)', () => { handler.parse('\x1b[?1048h\r\n\x1b[31mJUNK\x1b[?1048lTEST'); expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('JUNK'); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); // Text color of 'JUNK' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); + expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1049 (alt screen buffer+cursor)', () => { handler.parse('\x1b[?1049h\r\n\x1b[31mJUNK\x1b[?1049lTEST'); expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal(''); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); }); it('should handle DECSET/DECRST 1049 - maintains saved cursor for alt buffer', () => { handler.parse('\x1b[?1049h\r\n\x1b[31m\x1b[s\x1b[?1049lTEST'); expect(bufferService.buffer.translateBufferLineToString(0, true)).to.equal('TEST'); // Text color of 'TEST' should be default - expect(bufferService.buffer.lines.get(0).loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); + expect(bufferService.buffer.lines.get(0)!.loadCell(0, new CellData()).fg).to.equal(DEFAULT_ATTR_DATA.fg); handler.parse('\x1b[?1049h\x1b[uTEST'); expect(bufferService.buffer.translateBufferLineToString(1, true)).to.equal('TEST'); // Text color of 'TEST' should be red - expect((bufferService.buffer.lines.get(1).loadCell(0, new CellData()).getFgColor())).to.equal(1); + expect((bufferService.buffer.lines.get(1)!.loadCell(0, new CellData()).getFgColor())).to.equal(1); }); it('should handle DECSET/DECRST 1049 - clears alt buffer with erase attributes', () => { handler.parse('\x1b[42m\x1b[?1049h'); // Buffer should be filled with green background - expect(bufferService.buffer.lines.get(20).loadCell(10, new CellData()).getBgColor()).to.equal(2); + expect(bufferService.buffer.lines.get(20)!.loadCell(10, new CellData()).getBgColor()).to.equal(2); }); }); @@ -1045,7 +1045,7 @@ describe('InputHandler', () => { }); it('DCH - should delete last cell', () => { inputHandler.parse('0123456789\x1b[P'); - assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); it('ECH', () => { bufferService.buffer.x = 10000; @@ -1059,7 +1059,7 @@ describe('InputHandler', () => { }); it('ECH - should delete last cell', () => { inputHandler.parse('0123456789\x1b[X'); - assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); it('ICH', () => { bufferService.buffer.x = 10000; @@ -1073,7 +1073,7 @@ describe('InputHandler', () => { }); it('ICH - should delete last cell', () => { inputHandler.parse('0123456789\x1b[@'); - assert.equal(bufferService.buffer.lines.get(0).translateToString(false), '012345678 '); + assert.equal(bufferService.buffer.lines.get(0)!.translateToString(false), '012345678 '); }); }); }); From b79a0a9832a8d9a4c1c89379e4431b4002eac71f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 08:12:53 -0700 Subject: [PATCH 24/35] Remove unwanted vscode setting --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 38f4dd11..f07ca545 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { "typescript.preferences.importModuleSpecifier": "non-relative", - "typescript.preferences.quoteStyle": "single", - "eslint.enable": true + "typescript.preferences.quoteStyle": "single" } From d085768e8b1f319e873e0c3478272708fbe59b20 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 08:23:38 -0700 Subject: [PATCH 25/35] Fix some lint issues --- test/api/Terminal.api.ts | 2 +- test/benchmark/EscapeSequenceParser.benchmark.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/api/Terminal.api.ts b/test/api/Terminal.api.ts index 6829fd0a..75e00152 100644 --- a/test/api/Terminal.api.ts +++ b/test/api/Terminal.api.ts @@ -539,7 +539,7 @@ describe('API Integration Tests', function(): void { assert.equal(await page.evaluate(`window.term.buffer.active.getLine(0).translateToString()`), 'norm '); assert.equal(await page.evaluate(`window.term.buffer.normal.getLine(0).translateToString()`), 'norm '); assert.equal(await page.evaluate(`window.term.buffer.alternate.getLine(0)`), undefined); - }) + }); }); it('dispose', async () => { diff --git a/test/benchmark/EscapeSequenceParser.benchmark.ts b/test/benchmark/EscapeSequenceParser.benchmark.ts index 68247715..e4591dde 100644 --- a/test/benchmark/EscapeSequenceParser.benchmark.ts +++ b/test/benchmark/EscapeSequenceParser.benchmark.ts @@ -19,9 +19,9 @@ function toUtf32(s: string): Uint32Array { } class DcsHandler implements IDcsHandler { - hook(params: IParams): void {} - put(data: Uint32Array, start: number, end: number): void {} - unhook(): void {} + public hook(params: IParams): void {} + public put(data: Uint32Array, start: number, end: number): void {} + public unhook(): void {} } From b9dee59900d98fdbae18c619474186bfe6a0ddad Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 09:36:56 -0700 Subject: [PATCH 26/35] Move InputHandler init into CoreTerminal --- src/Terminal.ts | 52 +++++++++----------------------------- src/common/CoreTerminal.ts | 30 ++++++++++++++++++++-- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 61c997bd..54863ce0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -27,7 +27,7 @@ import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; -import { InputHandler, WindowsOptionsReportType } from './common/InputHandler'; +import { WindowsOptionsReportType } from './common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; @@ -49,7 +49,6 @@ import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISo import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; -import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { WriteBuffer } from 'common/input/WriteBuffer'; import { Linkifier2 } from 'browser/Linkifier2'; @@ -100,7 +99,6 @@ export class Terminal extends CoreTerminal implements ITerminal { */ private _keyDownHandled: boolean = false; - private _inputHandler: InputHandler; public linkifier: ILinkifier; public linkifier2: ILinkifier2; public viewport: IViewport; @@ -154,6 +152,16 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); + this.register(this._inputHandler.onRequestBell(() => this.bell())); + this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); + this.register(this._inputHandler.onRequestReset(() => this.reset())); + this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); + this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); + 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)); + // Setup listeners this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)); @@ -172,24 +180,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } protected _setup(): void { - if (this._inputHandler) { - this._inputHandler.reset(); - } else { - // Register input handler and refire/handle events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); - this.register(this._inputHandler.onRequestBell(() => this.bell())); - this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); - this.register(this._inputHandler.onRequestReset(() => this.reset())); - this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined))); - this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type))); - this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove)); - this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); - 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(this._inputHandler); - } - super._setup(); this._customKeyEventHandler = null; @@ -986,25 +976,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._customKeyEventHandler = customKeyEventHandler; } - /** Add handler for ESC escape sequence. See xterm.d.ts for details. */ - public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { - return this._inputHandler.addEscHandler(id, callback); - } - - /** Add handler for DCS escape sequence. See xterm.d.ts for details. */ - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { - return this._inputHandler.addDcsHandler(id, callback); - } - - /** Add handler for CSI escape sequence. See xterm.d.ts for details. */ - public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { - return this._inputHandler.addCsiHandler(id, callback); - } - /** Add handler for OSC escape sequence. See xterm.d.ts for details. */ - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - return this._inputHandler.addOscHandler(ident, callback); - } - /** * Registers a link matcher, allowing custom link patterns to be matched and * handled. @@ -1337,6 +1308,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const userScrolling = this._userScrolling; this._setup(); + this._inputHandler.reset(); this._bufferService.reset(); this._charsetService.reset(); this._coreService.reset(); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 49f3509d..8df2bd6b 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -27,7 +27,7 @@ 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 { ITerminalOptions, IDisposable } from './Types'; +import { ITerminalOptions, IDisposable } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -37,6 +37,7 @@ import { CharsetService } from 'common/services/CharsetService'; import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; import { IBufferSet } from 'common/buffer/Types'; +import { InputHandler } from 'common/InputHandler'; export abstract class CoreTerminal extends Disposable { protected readonly _instantiationService: IInstantiationService; @@ -50,6 +51,7 @@ export abstract class CoreTerminal extends Disposable { public readonly unicodeService: IUnicodeService; public readonly optionsService: IOptionsService; + protected _inputHandler: InputHandler; private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); @@ -94,6 +96,11 @@ export abstract class CoreTerminal extends Disposable { this.register(forwardEvent(this._coreService.onData, this._onData)); this.register(forwardEvent(this._coreService.onBinary, this._onBinary)); this.register(this.optionsService.onOptionChange(key => this._updateOptions(key))); + + // Register input handler and handle/forward events + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); + this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); + this.register(this._inputHandler); } public dispose(): void { @@ -116,6 +123,26 @@ export abstract class CoreTerminal extends Disposable { this._bufferService.resize(x, y); } + /** Add handler for ESC escape sequence. See xterm.d.ts for details. */ + public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { + return this._inputHandler.addEscHandler(id, callback); + } + + /** Add handler for DCS escape sequence. See xterm.d.ts for details. */ + public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { + return this._inputHandler.addDcsHandler(id, callback); + } + + /** Add handler for CSI escape sequence. See xterm.d.ts for details. */ + public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + return this._inputHandler.addCsiHandler(id, callback); + } + + /** Add handler for OSC escape sequence. See xterm.d.ts for details. */ + public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + return this._inputHandler.addOscHandler(ident, callback); + } + protected _setup(): void { if (this.optionsService.options.windowsMode) { this._enableWindowsMode(); @@ -156,5 +183,4 @@ export abstract class CoreTerminal extends Disposable { } public abstract scrollToBottom(): void; - public abstract addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; } From 46e7fc61cc78fdb692c454e4b54ac794a8218f83 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 09:38:34 -0700 Subject: [PATCH 27/35] Move WriteBuffer into CoreTerminal --- src/Terminal.ts | 14 -------------- src/common/CoreTerminal.ts | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 54863ce0..2385ad19 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -50,7 +50,6 @@ import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; -import { WriteBuffer } from 'common/input/WriteBuffer'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; @@ -86,9 +85,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _selectionService: ISelectionService; private _soundService: ISoundService; - // write buffer - private _writeBuffer: WriteBuffer; - // Store if user went browsing history in scrollback private _userScrolling: boolean; @@ -164,8 +160,6 @@ export class Terminal extends CoreTerminal implements ITerminal { // Setup listeners this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)); - - this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); } public dispose(): void { @@ -1364,14 +1358,6 @@ export class Terminal extends CoreTerminal implements ITerminal { // return this.options.bellStyle === 'sound' || // this.options.bellStyle === 'both'; } - - public write(data: string | Uint8Array, callback?: () => void): void { - this._writeBuffer.write(data, callback); - } - - public writeSync(data: string | Uint8Array): void { - this._writeBuffer.writeSync(data); - } } /** diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 8df2bd6b..f98ef0dc 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -38,6 +38,7 @@ import { updateWindowsModeWrappedState } from 'common/WindowsMode'; import { IFunctionIdentifier, IParams } from 'common/parser/Types'; import { IBufferSet } from 'common/buffer/Types'; import { InputHandler } from 'common/InputHandler'; +import { WriteBuffer } from 'common/input/WriteBuffer'; export abstract class CoreTerminal extends Disposable { protected readonly _instantiationService: IInstantiationService; @@ -52,6 +53,7 @@ export abstract class CoreTerminal extends Disposable { public readonly optionsService: IOptionsService; protected _inputHandler: InputHandler; + private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; private _onBinary = new EventEmitter(); @@ -91,16 +93,19 @@ export abstract class CoreTerminal extends Disposable { this._charsetService = this._instantiationService.createInstance(CharsetService); this._instantiationService.setService(ICharsetService, this._charsetService); + // Register input handler and handle/forward events + this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, 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(this.optionsService.onOptionChange(key => this._updateOptions(key))); - // Register input handler and handle/forward events - this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService); - this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed)); - this.register(this._inputHandler); + // Setup WriteBuffer + this._writeBuffer = new WriteBuffer(data => this._inputHandler.parse(data)); } public dispose(): void { @@ -112,6 +117,14 @@ export abstract class CoreTerminal extends Disposable { this._windowsMode = undefined; } + public write(data: string | Uint8Array, callback?: () => void): void { + this._writeBuffer.write(data, callback); + } + + public writeSync(data: string | Uint8Array): void { + this._writeBuffer.writeSync(data); + } + public resize(x: number, y: number): void { if (isNaN(x) || isNaN(y)) { return; From 21bcef5008de7ee49b2899f1549356d84c2c897c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 09:54:44 -0700 Subject: [PATCH 28/35] Move a bunch of scroll logic into CoreTerminal This change will now reset userScrolling on RIS --- src/Terminal.ts | 78 ++-------------------------- src/common/CoreTerminal.ts | 72 ++++++++++++++++++++++++- src/common/TestUtils.test.ts | 1 + src/common/services/BufferService.ts | 3 ++ src/common/services/Services.ts | 1 + 5 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 2385ad19..9f55e2bd 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -85,9 +85,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _selectionService: ISelectionService; private _soundService: ISoundService; - // Store if user went browsing history in scrollback - private _userScrolling: boolean; - /** * Records whether the keydown event has already been handled and triggered a data event, if so * the keypress event should not trigger a data event but should still print to the textarea so @@ -113,8 +110,6 @@ export class Terminal extends CoreTerminal implements ITerminal { 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 _onScroll = new EventEmitter(); - public get onScroll(): IEvent { return this._onScroll.event; } private _onSelectionChange = new EventEmitter(); public get onSelectionChange(): IEvent { return this._onSelectionChange.event; } private _onTitleChange = new EventEmitter(); @@ -178,7 +173,6 @@ export class Terminal extends CoreTerminal implements ITerminal { this._customKeyEventHandler = null; - this._userScrolling = false; if (!this.linkifier) { this.linkifier = this._instantiationService.createInstance(Linkifier); } @@ -862,13 +856,13 @@ export class Terminal extends CoreTerminal implements ITerminal { if (!willBufferBeTrimmed) { this.buffer.ybase++; // Only scroll the ydisp with ybase if the user has not scrolled up - if (!this._userScrolling) { + if (!this._bufferService.isUserScrolling) { this.buffer.ydisp++; } } else { // When the buffer is full and the user has scrolled up, keep the text // stable unless ydisp is right at the top - if (this._userScrolling) { + if (this._bufferService.isUserScrolling) { this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); } } @@ -882,7 +876,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Move the viewport to the bottom of the buffer unless the user is // scrolling. - if (!this._userScrolling) { + if (!this._bufferService.isUserScrolling) { this.buffer.ydisp = this.buffer.ybase; } @@ -892,67 +886,11 @@ export class Terminal extends CoreTerminal implements ITerminal { this._onScroll.fire(this.buffer.ydisp); } - /** - * Scroll the display of the terminal - * @param disp The number of lines to scroll down (negative scroll up). - * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used - * to avoid unwanted events being handled by the viewport when the event was triggered from the - * viewport originally. - */ public scrollLines(disp: number, suppressScrollEvent?: boolean): void { - if (disp < 0) { - if (this.buffer.ydisp === 0) { - return; - } - this._userScrolling = true; - } else if (disp + this.buffer.ydisp >= this.buffer.ybase) { - this._userScrolling = false; - } - - const oldYdisp = this.buffer.ydisp; - this.buffer.ydisp = Math.max(Math.min(this.buffer.ydisp + disp, this.buffer.ybase), 0); - - // No change occurred, don't trigger scroll/refresh - if (oldYdisp === this.buffer.ydisp) { - return; - } - - if (!suppressScrollEvent) { - this._onScroll.fire(this.buffer.ydisp); - } - + super.scrollLines(disp, suppressScrollEvent); this.refresh(0, this.rows - 1); } - /** - * Scroll the display of the terminal by a number of pages. - * @param pageCount The number of pages to scroll (negative scrolls up). - */ - public scrollPages(pageCount: number): void { - this.scrollLines(pageCount * (this.rows - 1)); - } - - /** - * Scrolls the display of the terminal to the top. - */ - public scrollToTop(): void { - this.scrollLines(-this.buffer.ydisp); - } - - /** - * Scrolls the display of the terminal to the bottom. - */ - public scrollToBottom(): void { - this.scrollLines(this.buffer.ybase - this.buffer.ydisp); - } - - public scrollToLine(line: number): void { - const scrollAmount = line - this.buffer.ydisp; - if (scrollAmount !== 0) { - this.scrollLines(scrollAmount); - } - } - public paste(data: string): void { paste(data, this.textarea, this._coreService); } @@ -1299,19 +1237,13 @@ export class Terminal extends CoreTerminal implements ITerminal { this.options.rows = this.rows; this.options.cols = this.cols; const customKeyEventHandler = this._customKeyEventHandler; - const userScrolling = this._userScrolling; this._setup(); - this._inputHandler.reset(); - this._bufferService.reset(); - this._charsetService.reset(); - this._coreService.reset(); - this._coreMouseService.reset(); + super.reset(); this._selectionService?.reset(); // reattach this._customKeyEventHandler = customKeyEventHandler; - this._userScrolling = userScrolling; // do a full screen refresh this.refresh(0, this.rows - 1); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index f98ef0dc..a870a765 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -64,6 +64,8 @@ export abstract class CoreTerminal extends Disposable { public get onLineFeed(): IEvent { 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(); + public get onScroll(): IEvent { return this._onScroll.event; } public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } @@ -136,6 +138,66 @@ export abstract class CoreTerminal extends Disposable { this._bufferService.resize(x, y); } + /** + * Scroll the display of the terminal + * @param disp The number of lines to scroll down (negative scroll up). + * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used + * to avoid unwanted events being handled by the viewport when the event was triggered from the + * viewport originally. + */ + public scrollLines(disp: number, suppressScrollEvent?: boolean): void { + const buffer = this._bufferService.buffer; + if (disp < 0) { + if (buffer.ydisp === 0) { + return; + } + this._bufferService.isUserScrolling = true; + } else if (disp + buffer.ydisp >= buffer.ybase) { + this._bufferService.isUserScrolling = false; + } + + const oldYdisp = buffer.ydisp; + buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0); + + // No change occurred, don't trigger scroll/refresh + if (oldYdisp === buffer.ydisp) { + return; + } + + if (!suppressScrollEvent) { + this._onScroll.fire(buffer.ydisp); + } + } + + /** + * Scroll the display of the terminal by a number of pages. + * @param pageCount The number of pages to scroll (negative scrolls up). + */ + public scrollPages(pageCount: number): void { + this.scrollLines(pageCount * (this.rows - 1)); + } + + /** + * Scrolls the display of the terminal to the top. + */ + public scrollToTop(): void { + this.scrollLines(-this._bufferService.buffer.ydisp); + } + + /** + * Scrolls the display of the terminal to the bottom. + */ + public scrollToBottom(): void { + this.scrollLines(this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp); + } + + public scrollToLine(line: number): void { + const scrollAmount = line - this._bufferService.buffer.ydisp; + if (scrollAmount !== 0) { + this.scrollLines(scrollAmount); + } + } + /** Add handler for ESC escape sequence. See xterm.d.ts for details. */ public addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable { return this._inputHandler.addEscHandler(id, callback); @@ -162,6 +224,14 @@ export abstract class CoreTerminal extends Disposable { } } + public reset(): void { + this._inputHandler.reset(); + this._bufferService.reset(); + this._charsetService.reset(); + this._coreService.reset(); + this._coreMouseService.reset(); + } + protected _updateOptions(key: string): void { // TODO: These listeners should be owned by individual components switch (key) { @@ -194,6 +264,4 @@ export abstract class CoreTerminal extends Disposable { }; } } - - public abstract scrollToBottom(): void; } diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 55e34084..71faed99 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -17,6 +17,7 @@ export class MockBufferService implements IBufferService { 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 isUserScrolling: boolean = false; constructor( public cols: number, public rows: number, diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index d62d2cb0..8de44e43 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -17,6 +17,8 @@ export class BufferService implements IBufferService { public cols: number; public rows: number; public buffers: IBufferSet; + /** 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; } @@ -41,5 +43,6 @@ export class BufferService implements IBufferService { public reset(): void { this.buffers = new BufferSet(this._optionsService, this); + this.isUserScrolling = false; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 6829d7b9..e123bd8b 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -16,6 +16,7 @@ export interface IBufferService { readonly rows: number; readonly buffer: IBuffer; readonly buffers: IBufferSet; + isUserScrolling: boolean; onResize: IEvent<{ cols: number, rows: number }>; From 2a9b76246b9ee56557ada869d6b6021ed6bced9a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 10:02:59 -0700 Subject: [PATCH 29/35] Move scroll into CoreTerminal --- src/Terminal.ts | 70 +------------------------------------ src/common/CoreTerminal.ts | 71 +++++++++++++++++++++++++++++++++++++- src/common/Types.d.ts | 2 +- 3 files changed, 72 insertions(+), 71 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 9f55e2bd..b7c8ba93 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm'; import { DomRenderer } from 'browser/renderer/dom/DomRenderer'; -import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; +import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; @@ -101,9 +101,6 @@ export class Terminal extends CoreTerminal implements ITerminal { private _colorManager: ColorManager; private _theme: ITheme; - // bufferline to clone/copy from for new blank lines - private _blankLine: IBufferLine = null; - private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>(); @@ -821,71 +818,6 @@ export class Terminal extends CoreTerminal implements ITerminal { } } - /** - * Scroll the terminal down 1 row, creating a blank line. - * @param isWrapped Whether the new line is wrapped from the previous line. - */ - public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { - let newLine: IBufferLine; - newLine = this._blankLine; - if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { - newLine = this.buffer.getBlankLine(eraseAttr, isWrapped); - this._blankLine = newLine; - } - newLine.isWrapped = isWrapped; - - const topRow = this.buffer.ybase + this.buffer.scrollTop; - const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; - - if (this.buffer.scrollTop === 0) { - // Determine whether the buffer is going to be trimmed after insertion. - const willBufferBeTrimmed = this.buffer.lines.isFull; - - // Insert the line using the fastest method - if (bottomRow === this.buffer.lines.length - 1) { - if (willBufferBeTrimmed) { - this.buffer.lines.recycle().copyFrom(newLine); - } else { - this.buffer.lines.push(newLine.clone()); - } - } else { - this.buffer.lines.splice(bottomRow + 1, 0, newLine.clone()); - } - - // Only adjust ybase and ydisp when the buffer is not trimmed - if (!willBufferBeTrimmed) { - this.buffer.ybase++; - // Only scroll the ydisp with ybase if the user has not scrolled up - if (!this._bufferService.isUserScrolling) { - this.buffer.ydisp++; - } - } else { - // When the buffer is full and the user has scrolled up, keep the text - // stable unless ydisp is right at the top - if (this._bufferService.isUserScrolling) { - this.buffer.ydisp = Math.max(this.buffer.ydisp - 1, 0); - } - } - } else { - // scrollTop is non-zero which means no line will be going to the - // scrollback, instead we can just shift them in-place. - const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */; - this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); - this.buffer.lines.set(bottomRow, newLine.clone()); - } - - // Move the viewport to the bottom of the buffer unless the user is - // scrolling. - if (!this._bufferService.isUserScrolling) { - this.buffer.ydisp = this.buffer.ybase; - } - - // Flag rows that need updating - this._dirtyRowService.markRangeDirty(this.buffer.scrollTop, this.buffer.scrollBottom); - - this._onScroll.fire(this.buffer.ydisp); - } - public scrollLines(disp: number, suppressScrollEvent?: boolean): void { super.scrollLines(disp, suppressScrollEvent); this.refresh(0, this.rows - 1); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index a870a765..113132c7 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -27,7 +27,7 @@ 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 { ITerminalOptions, IDisposable } from 'common/Types'; +import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -55,6 +55,8 @@ export abstract class CoreTerminal extends Disposable { protected _inputHandler: InputHandler; private _writeBuffer: WriteBuffer; private _windowsMode: IDisposable | undefined; + /** An IBufferline to clone/copy from for new blank lines */ + private _cachedBlankLine: IBufferLine | undefined; private _onBinary = new EventEmitter(); public get onBinary(): IEvent { return this._onBinary.event; } @@ -138,6 +140,73 @@ export abstract class CoreTerminal extends Disposable { this._bufferService.resize(x, y); } + /** + * Scroll the terminal down 1 row, creating a blank line. + * @param isWrapped Whether the new line is wrapped from the previous line. + */ + public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void { + const buffer = this._bufferService.buffer; + + let newLine: IBufferLine | undefined; + newLine = this._cachedBlankLine; + if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) { + newLine = buffer.getBlankLine(eraseAttr, isWrapped); + this._cachedBlankLine = newLine; + } + newLine.isWrapped = isWrapped; + + const topRow = buffer.ybase + buffer.scrollTop; + const bottomRow = buffer.ybase + buffer.scrollBottom; + + if (buffer.scrollTop === 0) { + // Determine whether the buffer is going to be trimmed after insertion. + const willBufferBeTrimmed = buffer.lines.isFull; + + // Insert the line using the fastest method + if (bottomRow === buffer.lines.length - 1) { + if (willBufferBeTrimmed) { + buffer.lines.recycle().copyFrom(newLine); + } else { + buffer.lines.push(newLine.clone()); + } + } else { + buffer.lines.splice(bottomRow + 1, 0, newLine.clone()); + } + + // Only adjust ybase and ydisp when the buffer is not trimmed + if (!willBufferBeTrimmed) { + buffer.ybase++; + // Only scroll the ydisp with ybase if the user has not scrolled up + if (!this._bufferService.isUserScrolling) { + buffer.ydisp++; + } + } else { + // When the buffer is full and the user has scrolled up, keep the text + // stable unless ydisp is right at the top + if (this._bufferService.isUserScrolling) { + buffer.ydisp = Math.max(buffer.ydisp - 1, 0); + } + } + } else { + // scrollTop is non-zero which means no line will be going to the + // scrollback, instead we can just shift them in-place. + const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */; + buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1); + buffer.lines.set(bottomRow, newLine.clone()); + } + + // Move the viewport to the bottom of the buffer unless the user is + // scrolling. + if (!this._bufferService.isUserScrolling) { + buffer.ydisp = buffer.ybase; + } + + // Flag rows that need updating + this._dirtyRowService.markRangeDirty(buffer.scrollTop, buffer.scrollBottom); + + this._onScroll.fire(buffer.ydisp); + } + /** * Scroll the display of the terminal * @param disp The number of lines to scroll down (negative scroll up). diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 49a723b2..b9d43b74 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -51,7 +51,7 @@ export interface ICircularList { get(index: number): T | undefined; set(index: number, value: T): void; push(value: T): void; - recycle(): T | undefined; + recycle(): T; pop(): T | undefined; splice(start: number, deleteCount: number, ...items: T[]): void; trimStart(count: number): void; From 4d01929503d7b496c949f3dbf6db25fddc57d83d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 10:11:24 -0700 Subject: [PATCH 30/35] Introduce ICoreTerminal --- .../src/renderLayer/LinkRenderLayer.ts | 6 ++--- src/TestUtils.test.ts | 2 +- src/Types.d.ts | 25 ++++++------------- src/common/CoreTerminal.ts | 4 +-- src/common/Types.d.ts | 6 +++++ 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 10cf9c1f..a095b4ae 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -3,18 +3,18 @@ * @license MIT */ -import { ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; -import { IColorSet, ILinkifierEvent } from 'browser/Types'; +import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; +import { ITerminal } from '../../../../src/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; - constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { + constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) { super(container, 'link', zIndex, true, colors); terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 32b2633f..47a90b6c 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -171,7 +171,7 @@ export class MockTerminal implements ITerminal { public addDisposableListener(type: string, handler: XtermListener): IDisposable { throw new Error('Method not implemented.'); } - public scrollLines(disp: number, suppressScrollEvent: boolean): void { + public scrollLines(disp: number): void { throw new Error('Method not implemented.'); } public scrollToRow(absoluteRow: number): number { diff --git a/src/Types.d.ts b/src/Types.d.ts index 05c89e15..a8f7f9a8 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -4,12 +4,14 @@ */ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; -import { IAttributeData, CharData, ITerminalOptions } from 'common/Types'; +import { IAttributeData, CharData, ITerminalOptions, ICoreTerminal } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; import { ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; +import { Linkifier } from 'browser/Linkifier'; +import { Linkifier2 } from 'browser/Linkifier2'; export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; @@ -23,23 +25,23 @@ export interface ICompositionHelper { keydown(ev: KeyboardEvent): boolean; } -export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { +export interface ITerminal extends IPublicTerminal, ICoreTerminal { + element: HTMLElement | undefined; screenElement: HTMLElement; browser: IBrowser; buffer: IBuffer; buffers: IBufferSet; viewport: IViewport; - optionsService: IOptionsService; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; - unicodeService: IUnicodeService; + linkifier: ILinkifier; + linkifier2: ILinkifier2; onBlur: IEvent; onFocus: IEvent; onA11yChar: IEvent; onA11yTab: IEvent; - scrollLines(disp: number, suppressScrollEvent?: boolean): void; cancel(ev: Event, force?: boolean): boolean | void; } @@ -95,19 +97,6 @@ export interface IPublicTerminal extends IDisposable { reset(): void; } -export interface IBufferAccessor { - buffer: IBuffer; -} - -export interface IElementAccessor { - readonly element: HTMLElement | undefined; -} - -export interface ILinkifierAccessor { - linkifier: ILinkifier; - linkifier2: ILinkifier2; -} - export interface IBrowser { isNode: boolean; userAgent: string; diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 113132c7..3793ae25 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -27,7 +27,7 @@ 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 { ITerminalOptions, IDisposable, IBufferLine, IAttributeData } from 'common/Types'; +import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal } from 'common/Types'; import { CoreService } from 'common/services/CoreService'; import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter'; import { CoreMouseService } from 'common/services/CoreMouseService'; @@ -40,7 +40,7 @@ import { IBufferSet } from 'common/buffer/Types'; import { InputHandler } from 'common/InputHandler'; import { WriteBuffer } from 'common/input/WriteBuffer'; -export abstract class CoreTerminal extends Disposable { +export abstract class CoreTerminal extends Disposable implements ICoreTerminal { protected readonly _instantiationService: IInstantiationService; protected readonly _bufferService: IBufferService; protected readonly _logService: ILogService; diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index b9d43b74..4fcb627b 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -7,6 +7,12 @@ import { ITerminalOptions as IPublicTerminalOptions } from 'xterm'; import { IEvent, IEventEmitter } from 'common/EventEmitter'; import { IDeleteEvent, IInsertEvent } from 'common/CircularList'; import { IParams } from 'common/parser/Types'; +import { IOptionsService, IUnicodeService } from 'common/services/Services'; + +export interface ICoreTerminal { + optionsService: IOptionsService; + unicodeService: IUnicodeService; +} export interface IDisposable { dispose(): void; From f256206fd4435727f7123ee5c39123312c612ee4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 10:18:05 -0700 Subject: [PATCH 31/35] Move Terminal and AccessibilityManager into browser --- src/Terminal2.test.ts | 2 +- src/TestUtils.test.ts | 2 +- src/Types.d.ts | 94 --------------------- src/{ => browser}/AccessibilityManager.ts | 7 +- src/browser/RenderDebouncer.ts | 2 +- src/{ => browser}/Terminal.ts | 4 +- src/browser/Types.d.ts | 99 ++++++++++++++++++++++- src/public/Terminal.ts | 2 +- 8 files changed, 109 insertions(+), 103 deletions(-) rename src/{ => browser}/AccessibilityManager.ts (98%) rename src/{ => browser}/Terminal.ts (99%) diff --git a/src/Terminal2.test.ts b/src/Terminal2.test.ts index f7046240..720ea6b1 100644 --- a/src/Terminal2.test.ts +++ b/src/Terminal2.test.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; import * as pty from 'node-pty'; -import { Terminal } from './Terminal'; +import { Terminal } from './browser/Terminal'; import { IDisposable } from 'xterm'; // all test files expect terminal in 80x25 diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 47a90b6c..965acc88 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -10,7 +10,7 @@ import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, I import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm'; -import { Terminal } from './Terminal'; +import { Terminal } from './browser/Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; diff --git a/src/Types.d.ts b/src/Types.d.ts index a8f7f9a8..1ffb7c5e 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -13,97 +13,3 @@ import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { Linkifier } from 'browser/Linkifier'; import { Linkifier2 } from 'browser/Linkifier2'; -export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; - -export type LineData = CharData[]; - -export interface ICompositionHelper { - compositionstart(): void; - compositionupdate(ev: CompositionEvent): void; - compositionend(): void; - updateCompositionElements(dontRecurse?: boolean): void; - keydown(ev: KeyboardEvent): boolean; -} - -export interface ITerminal extends IPublicTerminal, ICoreTerminal { - element: HTMLElement | undefined; - screenElement: HTMLElement; - browser: IBrowser; - buffer: IBuffer; - buffers: IBufferSet; - viewport: IViewport; - // TODO: We should remove options once components adopt optionsService - options: ITerminalOptions; - linkifier: ILinkifier; - linkifier2: ILinkifier2; - - onBlur: IEvent; - onFocus: IEvent; - onA11yChar: IEvent; - onA11yTab: IEvent; - - cancel(ev: Event, force?: boolean): boolean | void; -} - -// Portions of the public API that are required by the internal Terminal -export interface IPublicTerminal extends IDisposable { - textarea: HTMLTextAreaElement | undefined; - rows: number; - cols: number; - buffer: IBuffer; - markers: IMarker[]; - onCursorMove: IEvent; - onData: IEvent; - onBinary: IEvent; - onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; - onLineFeed: IEvent; - onScroll: IEvent; - onSelectionChange: IEvent; - onRender: IEvent<{ start: number, end: number }>; - onResize: IEvent<{ cols: number, rows: number }>; - onTitleChange: IEvent; - blur(): void; - focus(): void; - resize(columns: number, rows: number): void; - open(parent: HTMLElement): void; - attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; - addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable; - addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable; - addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): void; - registerLinkProvider(linkProvider: ILinkProvider): IDisposable; - registerCharacterJoiner(handler: (text: string) => [number, number][]): number; - deregisterCharacterJoiner(joinerId: number): void; - addMarker(cursorYOffset: number): IMarker; - hasSelection(): boolean; - getSelection(): string; - getSelectionPosition(): ISelectionPosition | undefined; - clearSelection(): void; - select(column: number, row: number, length: number): void; - selectAll(): void; - selectLines(start: number, end: number): void; - dispose(): void; - scrollLines(amount: number): void; - scrollPages(pageCount: number): void; - scrollToTop(): void; - scrollToBottom(): void; - scrollToLine(line: number): void; - clear(): void; - write(data: string | Uint8Array, callback?: () => void): void; - paste(data: string): void; - refresh(start: number, end: number): void; - reset(): void; -} - -export interface IBrowser { - isNode: boolean; - userAgent: string; - platform: string; - isFirefox: boolean; - isMac: boolean; - isIpad: boolean; - isIphone: boolean; - isWindows: boolean; -} diff --git a/src/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts similarity index 98% rename from src/AccessibilityManager.ts rename to src/browser/AccessibilityManager.ts index 78391501..b613e9ba 100644 --- a/src/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -3,8 +3,8 @@ * @license MIT */ -import * as Strings from './browser/LocalizableStrings'; -import { ITerminal } from './Types'; +import * as Strings from 'browser/LocalizableStrings'; +import { ITerminal } from 'browser/Types'; import { IBuffer } from 'common/buffer/Types'; import { isMac } from 'common/Platform'; import { RenderDebouncer } from 'browser/RenderDebouncer'; @@ -79,6 +79,9 @@ export class AccessibilityManager extends Disposable { this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityTreeRoot.appendChild(this._liveRegion); + if (!this._terminal.element) { + throw new Error('Cannot enable accessibility before Terminal.open'); + } this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this.register(this._renderRowsDebouncer); diff --git a/src/browser/RenderDebouncer.ts b/src/browser/RenderDebouncer.ts index 814afcef..2a06fdd6 100644 --- a/src/browser/RenderDebouncer.ts +++ b/src/browser/RenderDebouncer.ts @@ -26,7 +26,7 @@ export class RenderDebouncer implements IDisposable { } } - public refresh(rowStart: number, rowEnd: number, rowCount: number): void { + public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void { this._rowCount = rowCount; // Get the min/max row start/end for the arg values rowStart = rowStart !== undefined ? rowStart : 0; diff --git a/src/Terminal.ts b/src/browser/Terminal.ts similarity index 99% rename from src/Terminal.ts rename to src/browser/Terminal.ts index b7c8ba93..2c22a12c 100644 --- a/src/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,13 +21,13 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from './Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from '../Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; import { C0 } from 'common/data/EscapeSequences'; -import { WindowsOptionsReportType } from './common/InputHandler'; +import { WindowsOptionsReportType } from '../common/InputHandler'; import { Renderer } from 'browser/renderer/Renderer'; import { Linkifier } from 'browser/Linkifier'; import { SelectionService } from 'browser/services/SelectionService'; diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index f4645543..847d64e5 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -3,9 +3,106 @@ * @license MIT */ +import { IDisposable, IMarker, ISelectionPosition } from 'xterm'; import { IEvent } from 'common/EventEmitter'; -import { IDisposable } from 'common/Types'; +import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; + +export interface ITerminal extends IPublicTerminal, ICoreTerminal { + element: HTMLElement | undefined; + screenElement: HTMLElement; + browser: IBrowser; + buffer: IBuffer; + buffers: IBufferSet; + viewport: IViewport; + // TODO: We should remove options once components adopt optionsService + options: ITerminalOptions; + linkifier: ILinkifier; + linkifier2: ILinkifier2; + + onBlur: IEvent; + onFocus: IEvent; + onA11yChar: IEvent; + onA11yTab: IEvent; + + cancel(ev: Event, force?: boolean): boolean | void; +} + +// Portions of the public API that are required by the internal Terminal +export interface IPublicTerminal extends IDisposable { + textarea: HTMLTextAreaElement | undefined; + rows: number; + cols: number; + buffer: IBuffer; + markers: IMarker[]; + onCursorMove: IEvent; + onData: IEvent; + onBinary: IEvent; + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + onLineFeed: IEvent; + onScroll: IEvent; + onSelectionChange: IEvent; + onRender: IEvent<{ start: number, end: number }>; + onResize: IEvent<{ cols: number, rows: number }>; + onTitleChange: IEvent; + blur(): void; + focus(): void; + resize(columns: number, rows: number): void; + open(parent: HTMLElement): void; + attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + // addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; + // addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable; + // addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable; + // addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; + deregisterLinkMatcher(matcherId: number): void; + registerLinkProvider(linkProvider: ILinkProvider): IDisposable; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): void; + addMarker(cursorYOffset: number): IMarker; + hasSelection(): boolean; + getSelection(): string; + getSelectionPosition(): ISelectionPosition | undefined; + clearSelection(): void; + select(column: number, row: number, length: number): void; + selectAll(): void; + selectLines(start: number, end: number): void; + dispose(): void; + scrollLines(amount: number): void; + scrollPages(pageCount: number): void; + scrollToTop(): void; + scrollToBottom(): void; + scrollToLine(line: number): void; + clear(): void; + write(data: string | Uint8Array, callback?: () => void): void; + paste(data: string): void; + refresh(start: number, end: number): void; + reset(): void; +} + +export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean; + +export type LineData = CharData[]; + +export interface ICompositionHelper { + compositionstart(): void; + compositionupdate(ev: CompositionEvent): void; + compositionend(): void; + updateCompositionElements(dontRecurse?: boolean): void; + keydown(ev: KeyboardEvent): boolean; +} + +export interface IBrowser { + isNode: boolean; + userAgent: string; + platform: string; + isFirefox: boolean; + isMac: boolean; + isIpad: boolean; + isIphone: boolean; + isWindows: boolean; +} export interface IColorManager { colors: IColorSet; diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 61334f08..d9b951ef 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -8,7 +8,7 @@ import { ITerminal } from '../Types'; import { IBufferLine, ICellData } from 'common/Types'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { CellData } from 'common/buffer/CellData'; -import { Terminal as TerminalCore } from '../Terminal'; +import { Terminal as TerminalCore } from '../browser/Terminal'; import * as Strings from '../browser/LocalizableStrings'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; From bdb438972f9b1e79f8d115b22d00b868d23e1cd1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 10:44:44 -0700 Subject: [PATCH 32/35] Fix errors in Terminal and elsewhere This was mostly ! to compel TS, we should actually fix this down the road but that can come later. --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 19 +- .../src/renderLayer/LinkRenderLayer.ts | 3 +- src/TestUtils.test.ts | 3 +- src/browser/AccessibilityManager.ts | 2 +- src/browser/Linkifier2.ts | 2 +- src/browser/Terminal.ts | 249 +++++++++--------- src/browser/Types.d.ts | 15 +- src/browser/services/RenderService.ts | 2 +- src/public/Terminal.ts | 2 +- test/benchmark/Terminal.benchmark.ts | 2 +- typings/xterm.d.ts | 2 +- 11 files changed, 147 insertions(+), 154 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 0333fd20..ac997c96 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ITerminal } from '../../../src/Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; @@ -17,7 +16,7 @@ import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IColorSet } from 'browser/Types'; +import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -52,8 +51,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core = (this._terminal as any)._core; this._renderLayers = [ - new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), - new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRedraw) + new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core), + new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._onRequestRedraw) ]; this.dimensions = { scaledCharWidth: 0, @@ -83,7 +82,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported ' + this._gl); } - this._core.screenElement.appendChild(this._canvas); + this._core.screenElement!.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -91,12 +90,12 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update dimensions and acquire char atlas this.onCharSizeChanged(); - this._isAttached = document.body.contains(this._core.screenElement); + this._isAttached = document.body.contains(this._core.screenElement!); } public dispose(): void { this._renderLayers.forEach(l => l.dispose()); - this._core.screenElement.removeChild(this._canvas); + this._core.screenElement!.removeChild(this._canvas); super.dispose(); } @@ -150,8 +149,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._canvas.style.height = `${this.dimensions.canvasHeight}px`; // Resize the screen - this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`; + this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); @@ -229,7 +228,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { if (!this._isAttached) { - if (document.body.contains(this._core.screenElement) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) { + if (document.body.contains(this._core.screenElement!) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) { this._updateDimensions(); this._refreshCharAtlas(); this._isAttached = true; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index a095b4ae..8f6b3e6d 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -7,9 +7,8 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; -import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types'; +import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { ITerminal } from '../../../../src/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 965acc88..102a5353 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -4,7 +4,6 @@ */ import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { ICompositionHelper, ITerminal, IBrowser } from './Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; @@ -12,7 +11,7 @@ import * as Browser from 'common/Platform'; import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm'; import { Terminal } from './browser/Terminal'; import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2 } from 'browser/Types'; +import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2, ICompositionHelper, ITerminal, IBrowser } from 'browser/Types'; import { IOptionsService, IUnicodeService } from 'common/services/Services'; import { IParams, IFunctionIdentifier } from 'common/parser/Types'; import { ISelectionService } from 'browser/services/Services'; diff --git a/src/browser/AccessibilityManager.ts b/src/browser/AccessibilityManager.ts index b613e9ba..2a34d6a3 100644 --- a/src/browser/AccessibilityManager.ts +++ b/src/browser/AccessibilityManager.ts @@ -106,7 +106,7 @@ export class AccessibilityManager extends Disposable { public dispose(): void { super.dispose(); - this._terminal.element.removeChild(this._accessibilityTreeRoot); + this._terminal.element?.removeChild(this._accessibilityTreeRoot); this._rowElements.length = 0; } diff --git a/src/browser/Linkifier2.ts b/src/browser/Linkifier2.ts index 54812169..458b1bff 100644 --- a/src/browser/Linkifier2.ts +++ b/src/browser/Linkifier2.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from './Types'; +import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBufferService } from 'common/services/Services'; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 2c22a12c..7f3ead61 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -21,7 +21,7 @@ * http://linux.die.net/man/7/urxvt */ -import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler } from '../Types'; +import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; @@ -49,41 +49,40 @@ import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISo import { CharSizeService } from 'browser/services/CharSizeService'; import { IBuffer } from 'common/buffer/Types'; import { MouseService } from 'browser/services/MouseService'; -import { ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; import { Linkifier2 } from 'browser/Linkifier2'; import { CoreBrowserService } from 'browser/services/CoreBrowserService'; import { CoreTerminal } from 'common/CoreTerminal'; +import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services'; // Let it work inside Node.js for automated testing purposes. -const document = (typeof window !== 'undefined') ? window.document : null; - +const document: Document = (typeof window !== 'undefined') ? window.document : null as any; export class Terminal extends CoreTerminal implements ITerminal { - public textarea: HTMLTextAreaElement; - public element: HTMLElement; - public screenElement: HTMLElement; + public textarea: HTMLTextAreaElement | undefined; + public element: HTMLElement | undefined; + public screenElement: HTMLElement | undefined; - private _document: Document; - private _viewportScrollArea: HTMLElement; - private _viewportElement: HTMLElement; - private _helperContainer: HTMLElement; - private _compositionView: HTMLElement; + private _document: Document | undefined; + private _viewportScrollArea: HTMLElement | undefined; + private _viewportElement: HTMLElement | undefined; + private _helperContainer: HTMLElement | undefined; + private _compositionView: HTMLElement | undefined; - private _visualBellTimer: number; + // private _visualBellTimer: number; public browser: IBrowser = Browser; // TODO: We should remove options once components adopt optionsService - public get options(): ITerminalOptions { return this.optionsService.options; } + public get options(): IInitializedTerminalOptions { return this.optionsService.options; } - private _customKeyEventHandler: CustomKeyEventHandler; + private _customKeyEventHandler: CustomKeyEventHandler | undefined; // browser services - private _charSizeService: ICharSizeService; - private _mouseService: IMouseService; - private _renderService: IRenderService; - private _selectionService: ISelectionService; - private _soundService: ISoundService; + private _charSizeService: ICharSizeService | undefined; + private _mouseService: IMouseService | undefined; + private _renderService: IRenderService | undefined; + private _selectionService: ISelectionService | undefined; + private _soundService: ISoundService | undefined; /** * Records whether the keydown event has already been handled and triggered a data event, if so @@ -94,12 +93,12 @@ export class Terminal extends CoreTerminal implements ITerminal { public linkifier: ILinkifier; public linkifier2: ILinkifier2; - public viewport: IViewport; - private _compositionHelper: ICompositionHelper; - private _mouseZoneManager: IMouseZoneManager; - private _accessibilityManager: AccessibilityManager; - private _colorManager: ColorManager; - private _theme: ITheme; + public viewport: IViewport | undefined; + private _compositionHelper: ICompositionHelper | undefined; + private _mouseZoneManager: IMouseZoneManager | undefined; + private _accessibilityManager: AccessibilityManager | undefined; + private _colorManager: ColorManager | undefined; + private _theme: ITheme | undefined; private _onCursorMove = new EventEmitter(); public get onCursorMove(): IEvent { return this._onCursorMove.event; } @@ -140,6 +139,10 @@ export class Terminal extends CoreTerminal implements ITerminal { this._setup(); + this.linkifier = this._instantiationService.createInstance(Linkifier); + this.linkifier2 = this._instantiationService.createInstance(Linkifier2); + + // Setup InputHandler listeners this.register(this._inputHandler.onRequestBell(() => this.bell())); this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end))); this.register(this._inputHandler.onRequestReset(() => this.reset())); @@ -160,7 +163,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } super.dispose(); this._renderService?.dispose(); - this._customKeyEventHandler = null; + this._customKeyEventHandler = undefined; this.write = () => { }; this.element?.parentNode?.removeChild(this.element); } @@ -168,14 +171,7 @@ export class Terminal extends CoreTerminal implements ITerminal { protected _setup(): void { super._setup(); - this._customKeyEventHandler = null; - - if (!this.linkifier) { - this.linkifier = this._instantiationService.createInstance(Linkifier); - } - if (!this.linkifier2) { - this.linkifier2 = this._instantiationService.createInstance(Linkifier2); - } + this._customKeyEventHandler = undefined; } /** @@ -239,7 +235,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } } else { this._accessibilityManager?.dispose(); - this._accessibilityManager = null; + this._accessibilityManager = undefined; } break; case 'tabStopWidth': this.buffers.setupTabStops(); break; @@ -257,7 +253,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._coreService.triggerDataEvent(C0.ESC + '[I'); } this.updateCursorStyle(ev); - this.element.classList.add('focus'); + this.element!.classList.add('focus'); this._showCursor(); this._onFocus.fire(); } @@ -267,7 +263,7 @@ export class Terminal extends CoreTerminal implements ITerminal { * textarea. */ public blur(): void { - return this.textarea.blur(); + return this.textarea?.blur(); } /** @@ -276,12 +272,12 @@ export class Terminal extends CoreTerminal implements ITerminal { private _onTextAreaBlur(): void { // Text can safely be removed on blur. Doing it earlier could interfere with // screen readers reading it out. - this.textarea.value = ''; + this.textarea!.value = ''; this.refresh(this.buffer.y, this.buffer.y); if (this._coreService.decPrivateModes.sendFocus) { this._coreService.triggerDataEvent(C0.ESC + '[O'); } - this.element.classList.remove('focus'); + this.element!.classList.remove('focus'); this._onBlur.fire(); } @@ -292,29 +288,29 @@ export class Terminal extends CoreTerminal implements ITerminal { this._bindKeys(); // Bind clipboard functionality - this.register(addDisposableDomListener(this.element, 'copy', (event: ClipboardEvent) => { + this.register(addDisposableDomListener(this.element!, 'copy', (event: ClipboardEvent) => { // If mouse events are active it means the selection manager is disabled and // copy should be handled by the host program. if (!this.hasSelection()) { return; } - copyHandler(event, this._selectionService); + copyHandler(event, this._selectionService!); })); - const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea, this._coreService); - this.register(addDisposableDomListener(this.textarea, 'paste', pasteHandlerWrapper)); - this.register(addDisposableDomListener(this.element, 'paste', pasteHandlerWrapper)); + const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this._coreService); + this.register(addDisposableDomListener(this.textarea!, 'paste', pasteHandlerWrapper)); + this.register(addDisposableDomListener(this.element!, 'paste', pasteHandlerWrapper)); // Handle right click context menus if (Browser.isFirefox) { // Firefox doesn't appear to fire the contextmenu event on right click - this.register(addDisposableDomListener(this.element, 'mousedown', (event: MouseEvent) => { + this.register(addDisposableDomListener(this.element!, 'mousedown', (event: MouseEvent) => { if (event.button === 2) { - rightClickHandler(event, this.textarea, this.screenElement, this._selectionService, this.options.rightClickSelectsWord); + rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord); } })); } else { - this.register(addDisposableDomListener(this.element, 'contextmenu', (event: MouseEvent) => { - rightClickHandler(event, this.textarea, this.screenElement, this._selectionService, this.options.rightClickSelectsWord); + this.register(addDisposableDomListener(this.element!, 'contextmenu', (event: MouseEvent) => { + rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord); })); } @@ -324,9 +320,9 @@ export class Terminal extends CoreTerminal implements ITerminal { if (Browser.isLinux) { // Use auxclick event over mousedown the latter doesn't seem to work. Note // that the regular click event doesn't fire for the middle mouse button. - this.register(addDisposableDomListener(this.element, 'auxclick', (event: MouseEvent) => { + this.register(addDisposableDomListener(this.element!, 'auxclick', (event: MouseEvent) => { if (event.button === 1) { - moveTextAreaUnderMouseCursor(event, this.textarea, this.screenElement); + moveTextAreaUnderMouseCursor(event, this.textarea!, this.screenElement!); } })); } @@ -336,13 +332,13 @@ export class Terminal extends CoreTerminal implements ITerminal { * Apply key handling to the terminal */ private _bindKeys(): void { - this.register(addDisposableDomListener(this.textarea, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true)); - this.register(addDisposableDomListener(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true)); - this.register(addDisposableDomListener(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true)); - this.register(addDisposableDomListener(this.textarea, 'compositionstart', () => this._compositionHelper.compositionstart())); - this.register(addDisposableDomListener(this.textarea, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper.compositionupdate(e))); - this.register(addDisposableDomListener(this.textarea, 'compositionend', () => this._compositionHelper.compositionend())); - this.register(this.onRender(() => this._compositionHelper.updateCompositionElements())); + this.register(addDisposableDomListener(this.textarea!, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true)); + this.register(addDisposableDomListener(this.textarea!, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true)); + this.register(addDisposableDomListener(this.textarea!, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true)); + this.register(addDisposableDomListener(this.textarea!, 'compositionstart', () => this._compositionHelper!.compositionstart())); + this.register(addDisposableDomListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e))); + this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend())); + this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements())); this.register(this.onRender(e => this._queueLinkification(e.start, e.end))); } @@ -360,7 +356,7 @@ export class Terminal extends CoreTerminal implements ITerminal { this._logService.debug('Terminal.open was called on an element that was not attached to the DOM'); } - this._document = parent.ownerDocument; + this._document = parent.ownerDocument!; // Create main element container this.element = this._document.createElement('div'); @@ -416,16 +412,15 @@ export class Terminal extends CoreTerminal implements ITerminal { this.element.appendChild(fragment); this._theme = this.options.theme || this._theme; - this.options.theme = undefined; this._colorManager = new ColorManager(document, this.options.allowTransparency); - this.optionsService.onOptionChange(e => this._colorManager.onOptionsChange(e)); + this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)); this._colorManager.setTheme(this._theme); const renderer = this._createRenderer(); this._renderService = this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement); this._instantiationService.setService(IRenderService, this._renderService); this._renderService.onRenderedBufferChange(e => this._onRender.fire(e)); - this.onResize(e => this._renderService.resize(e.cols, e.rows)); + this.onResize(e => this._renderService!.resize(e.cols, e.rows)); this._soundService = this._instantiationService.createInstance(SoundService); this._instantiationService.setService(ISoundService, this._soundService); @@ -438,14 +433,14 @@ export class Terminal extends CoreTerminal implements ITerminal { this._viewportScrollArea ); this.viewport.onThemeChange(this._colorManager.colors); - this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport.syncScrollArea())); + this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea())); this.register(this.viewport); - this.register(this.onCursorMove(() => this._renderService.onCursorMove())); - this.register(this.onResize(() => this._renderService.onResize(this.cols, this.rows))); - this.register(this.onBlur(() => this._renderService.onBlur())); - this.register(this.onFocus(() => this._renderService.onFocus())); - this.register(this._renderService.onDimensionsChange(() => this.viewport.syncScrollArea())); + this.register(this.onCursorMove(() => this._renderService!.onCursorMove())); + this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows))); + this.register(this.onBlur(() => this._renderService!.onBlur())); + this.register(this.onFocus(() => this._renderService!.onFocus())); + this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea())); this._selectionService = this._instantiationService.createInstance(SelectionService, (amount: number, suppressEvent: boolean) => this.scrollLines(amount, suppressEvent), @@ -453,29 +448,29 @@ export class Terminal extends CoreTerminal implements ITerminal { this.screenElement); this._instantiationService.setService(ISelectionService, this._selectionService); this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire())); - this.register(this._selectionService.onRedrawRequest(e => this._renderService.onSelectionChanged(e.start, e.end, e.columnSelectMode))); + this.register(this._selectionService.onRedrawRequest(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 // in order to register it as a selection on the OS. This event is fired // only on Linux to enable middle click to paste selection. - this.textarea.value = text; - this.textarea.focus(); - this.textarea.select(); + this.textarea!.value = text; + this.textarea!.focus(); + this.textarea!.select(); })); this.register(this.onScroll(() => { - this.viewport.syncScrollArea(); - this._selectionService.refresh(); + this.viewport!.syncScrollArea(); + this._selectionService!.refresh(); })); - this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService.refresh())); + this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh())); this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement); this.register(this._mouseZoneManager); - this.register(this.onScroll(() => this._mouseZoneManager.clearAll())); + this.register(this.onScroll(() => this._mouseZoneManager!.clearAll())); this.linkifier.attachToDom(this.element, this._mouseZoneManager); this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService); // This event listener must be registered aftre MouseZoneManager is created - this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService.onMouseDown(e))); + this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e))); // apply mouse event classes set by escape codes before terminal was attached if (this._coreMouseService.areMouseEventsActive) { @@ -507,8 +502,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager.colors, this.screenElement, this.linkifier, this.linkifier2); - case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager.colors, this.element, this.screenElement, this._viewportElement, this.linkifier, this.linkifier2); + case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2); + case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } @@ -520,8 +515,8 @@ export class Terminal extends CoreTerminal implements ITerminal { private _setTheme(theme: ITheme): void { this._theme = theme; this._colorManager?.setTheme(theme); - this._renderService?.setColors(this._colorManager.colors); - this.viewport?.onThemeChange(this._colorManager.colors); + this._renderService?.setColors(this._colorManager!.colors); + this.viewport?.onThemeChange(this._colorManager!.colors); } /** @@ -541,18 +536,18 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public bindMouse(): void { const self = this; - const el = this.element; + const el = this.element!; // send event to CoreMouseService function sendEvent(ev: MouseEvent | WheelEvent): boolean { // get mouse coordinates - const pos = self._mouseService.getRawByteCoords(ev, self.screenElement, self.cols, self.rows); + const pos = self._mouseService!.getRawByteCoords(ev, self.screenElement!, self.cols, self.rows); if (!pos) { return false; } let but: CoreMouseButton; - let action: CoreMouseAction; + let action: CoreMouseAction | undefined; switch ((ev).overrideType || ev.type) { case 'mousemove': action = CoreMouseAction.MOVE; @@ -621,14 +616,14 @@ export class Terminal extends CoreTerminal implements ITerminal { mousedrag: null, mousemove: null }; - const eventListeners: { [key: string]: (ev: Event) => void } = { + const eventListeners: { [key: string]: (ev: any) => void | boolean } = { mouseup: (ev: MouseEvent) => { sendEvent(ev); if (!ev.buttons) { // if no other button is held remove global handlers - this._document.removeEventListener('mouseup', requestedEvents.mouseup); + this._document!.removeEventListener('mouseup', requestedEvents.mouseup!); if (requestedEvents.mousedrag) { - this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + this._document!.removeEventListener('mousemove', requestedEvents.mousedrag); } } return this.cancel(ev); @@ -657,18 +652,18 @@ export class Terminal extends CoreTerminal implements ITerminal { if (this.optionsService.options.logLevel === 'debug') { this._logService.debug('Binding to mouse events:', this._coreMouseService.explainEvents(events)); } - this.element.classList.add('enable-mouse-events'); - this._selectionService.disable(); + this.element!.classList.add('enable-mouse-events'); + this._selectionService!.disable(); } else { this._logService.debug('Unbinding from mouse events.'); - this.element.classList.remove('enable-mouse-events'); - this._selectionService.enable(); + this.element!.classList.remove('enable-mouse-events'); + this._selectionService!.enable(); } // add/remove handlers from requestedEvents if (!(events & CoreMouseEventType.MOVE)) { - el.removeEventListener('mousemove', requestedEvents.mousemove); + el.removeEventListener('mousemove', requestedEvents.mousemove!); requestedEvents.mousemove = null; } else if (!requestedEvents.mousemove) { el.addEventListener('mousemove', eventListeners.mousemove); @@ -676,7 +671,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } if (!(events & CoreMouseEventType.WHEEL)) { - el.removeEventListener('wheel', requestedEvents.wheel); + el.removeEventListener('wheel', requestedEvents.wheel!); requestedEvents.wheel = null; } else if (!requestedEvents.wheel) { el.addEventListener('wheel', eventListeners.wheel); @@ -684,14 +679,14 @@ export class Terminal extends CoreTerminal implements ITerminal { } if (!(events & CoreMouseEventType.UP)) { - this._document.removeEventListener('mouseup', requestedEvents.mouseup); + this._document!.removeEventListener('mouseup', requestedEvents.mouseup!); requestedEvents.mouseup = null; } else if (!requestedEvents.mouseup) { requestedEvents.mouseup = eventListeners.mouseup; } if (!(events & CoreMouseEventType.DRAG)) { - this._document.removeEventListener('mousemove', requestedEvents.mousedrag); + this._document!.removeEventListener('mousemove', requestedEvents.mousedrag!); requestedEvents.mousedrag = null; } else if (!requestedEvents.mousedrag) { requestedEvents.mousedrag = eventListeners.mousedrag; @@ -710,7 +705,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Don't send the mouse button to the pty if mouse events are disabled or // if the selection manager is having selection forced (ie. a modifier is // held). - if (!this._coreMouseService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) { + if (!this._coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) { return; } @@ -721,10 +716,10 @@ export class Terminal extends CoreTerminal implements ITerminal { // Note: Other emulators also do this for 'mousedown' while a button // is held, we currently limit 'mousedown' to the terminal only. if (requestedEvents.mouseup) { - this._document.addEventListener('mouseup', requestedEvents.mouseup); + this._document!.addEventListener('mouseup', requestedEvents.mouseup); } if (requestedEvents.mousedrag) { - this._document.addEventListener('mousemove', requestedEvents.mousedrag); + this._document!.addEventListener('mousemove', requestedEvents.mousedrag); } return this.cancel(ev); @@ -735,7 +730,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Convert wheel events into up/down events when the buffer does not have scrollback, this // enables scrolling in apps hosted in the alt buffer such as vim or tmux. if (!this.buffer.hasScrollback) { - const amount = this.viewport.getLinesScrolled(ev); + const amount = this.viewport!.getLinesScrolled(ev); // Do nothing if there's no vertical scroll if (amount === 0) { @@ -758,20 +753,20 @@ export class Terminal extends CoreTerminal implements ITerminal { // the shell for example this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => { if (requestedEvents.wheel) return; - if (!this.viewport.onWheel(ev)) { + if (!this.viewport!.onWheel(ev)) { return this.cancel(ev); } })); this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => { if (this._coreMouseService.areMouseEventsActive) return; - this.viewport.onTouchStart(ev); + this.viewport!.onTouchStart(ev); return this.cancel(ev); })); this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => { if (this._coreMouseService.areMouseEventsActive) return; - if (!this.viewport.onTouchMove(ev)) { + if (!this.viewport!.onTouchMove(ev)) { return this.cancel(ev); } })); @@ -802,9 +797,9 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public updateCursorStyle(ev: KeyboardEvent): void { if (this._selectionService && this._selectionService.shouldColumnSelect(ev)) { - this.element.classList.add('column-select'); + this.element!.classList.add('column-select'); } else { - this.element.classList.remove('column-select'); + this.element!.classList.remove('column-select'); } } @@ -824,7 +819,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } public paste(data: string): void { - paste(data, this.textarea, this._coreService); + paste(data, this.textarea!, this._coreService); } /** @@ -871,13 +866,13 @@ export class Terminal extends CoreTerminal implements ITerminal { } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { - const joinerId = this._renderService.registerCharacterJoiner(handler); + const joinerId = this._renderService!.registerCharacterJoiner(handler); this.refresh(0, this.rows - 1); return joinerId; } public deregisterCharacterJoiner(joinerId: number): void { - if (this._renderService.deregisterCharacterJoiner(joinerId)) { + if (this._renderService!.deregisterCharacterJoiner(joinerId)) { this.refresh(0, this.rows - 1); } } @@ -886,7 +881,7 @@ export class Terminal extends CoreTerminal implements ITerminal { return this.buffer.markers; } - public addMarker(cursorYOffset: number): IMarker { + public addMarker(cursorYOffset: number): IMarker | undefined { // Disallow markers on the alt buffer if (this.buffer !== this.buffers.normal) { return; @@ -909,7 +904,7 @@ export class Terminal extends CoreTerminal implements ITerminal { * @param length The length of the selection. */ public select(column: number, row: number, length: number): void { - this._selectionService.setSelection(column, row, length); + this._selectionService!.setSelection(column, row, length); } /** @@ -921,15 +916,15 @@ export class Terminal extends CoreTerminal implements ITerminal { } public getSelectionPosition(): ISelectionPosition | undefined { - if (!this._selectionService.hasSelection) { + if (!this._selectionService || !this._selectionService.hasSelection) { return undefined; } return { - startColumn: this._selectionService.selectionStart[0], - startRow: this._selectionService.selectionStart[1], - endColumn: this._selectionService.selectionEnd[0], - endRow: this._selectionService.selectionEnd[1] + startColumn: this._selectionService.selectionStart![0], + startRow: this._selectionService.selectionStart![1], + endColumn: this._selectionService.selectionEnd![0], + endRow: this._selectionService.selectionEnd![1] }; } @@ -957,14 +952,14 @@ export class Terminal extends CoreTerminal implements ITerminal { * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent * @param ev The keydown event to be handled. */ - protected _keyDown(event: KeyboardEvent): boolean { + protected _keyDown(event: KeyboardEvent): boolean | undefined { this._keyDownHandled = false; if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { return false; } - if (!this._compositionHelper.keydown(event)) { + if (!this._compositionHelper!.keydown(event)) { if (this.buffer.ybase !== this.buffer.ydisp) { this.scrollToBottom(); } @@ -1002,7 +997,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // will announce deleted characters. This will not work 100% of the time but it should cover // most scenarios. if (result.key === C0.ETX || result.key === C0.CR) { - this.textarea.value = ''; + this.textarea!.value = ''; } this._onKey.fire({ key: result.key, domEvent: event }); @@ -1095,16 +1090,16 @@ export class Terminal extends CoreTerminal implements ITerminal { */ public bell(): void { if (this._soundBell()) { - this._soundService.playBellSound(); + this._soundService!.playBellSound(); } - if (this._visualBell()) { - this.element.classList.add('visual-bell-active'); - clearTimeout(this._visualBellTimer); - this._visualBellTimer = window.setTimeout(() => { - this.element.classList.remove('visual-bell-active'); - }, 200); - } + // if (this._visualBell()) { + // this.element.classList.add('visual-bell-active'); + // clearTimeout(this._visualBellTimer); + // this._visualBellTimer = window.setTimeout(() => { + // this.element.classList.remove('visual-bell-active'); + // }, 200); + // } } /** @@ -1141,7 +1136,7 @@ export class Terminal extends CoreTerminal implements ITerminal { // Don't clear if it's already clear return; } - this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)); + this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!); this.buffer.lines.length = 1; this.buffer.ydisp = 0; this.buffer.ybase = 0; @@ -1202,7 +1197,7 @@ export class Terminal extends CoreTerminal implements ITerminal { } // TODO: Remove cancel function and cancelEvents option - public cancel(ev: Event, force?: boolean): boolean { + public cancel(ev: Event, force?: boolean): boolean | undefined { if (!this.options.cancelEvents && !force) { return; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index 847d64e5..76aa0c8c 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -8,14 +8,15 @@ import { IEvent } from 'common/EventEmitter'; import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types'; import { IMouseService, IRenderService } from './services/Services'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; +import { IFunctionIdentifier, IParams } from 'common/parser/Types'; export interface ITerminal extends IPublicTerminal, ICoreTerminal { element: HTMLElement | undefined; - screenElement: HTMLElement; + screenElement: HTMLElement | undefined; browser: IBrowser; buffer: IBuffer; buffers: IBufferSet; - viewport: IViewport; + viewport: IViewport | undefined; // TODO: We should remove options once components adopt optionsService options: ITerminalOptions; linkifier: ILinkifier; @@ -51,16 +52,16 @@ export interface IPublicTerminal extends IDisposable { resize(columns: number, rows: number): void; open(parent: HTMLElement): void; attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - // addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; - // addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable; - // addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable; - // addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable; + addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable; + addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable; + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number; deregisterLinkMatcher(matcherId: number): void; registerLinkProvider(linkProvider: ILinkProvider): IDisposable; registerCharacterJoiner(handler: (text: string) => [number, number][]): number; deregisterCharacterJoiner(joinerId: number): void; - addMarker(cursorYOffset: number): IMarker; + addMarker(cursorYOffset: number): IMarker | undefined; hasSelection(): boolean; getSelection(): string; getSelectionPosition(): ISelectionPosition | undefined; diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index a0ca25d2..d6b30524 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -10,7 +10,7 @@ import { Disposable } from 'common/Lifecycle'; import { ScreenDprMonitor } from 'browser/ScreenDprMonitor'; import { addDisposableDomListener } from 'browser/Lifecycle'; import { IColorSet } from 'browser/Types'; -import { IOptionsService, IBufferService, ICoreService } from 'common/services/Services'; +import { IOptionsService, IBufferService } from 'common/services/Services'; import { ICharSizeService, IRenderService } from 'browser/services/Services'; interface ISelectionState { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index d9b951ef..3d483814 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -4,7 +4,7 @@ */ import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm'; -import { ITerminal } from '../Types'; +import { ITerminal } from 'browser/Types'; import { IBufferLine, ICellData } from 'common/Types'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { CellData } from 'common/buffer/CellData'; diff --git a/test/benchmark/Terminal.benchmark.ts b/test/benchmark/Terminal.benchmark.ts index 71a36cc7..76139cb6 100644 --- a/test/benchmark/Terminal.benchmark.ts +++ b/test/benchmark/Terminal.benchmark.ts @@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; import { spawn } from 'node-pty'; import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; -import { Terminal } from 'Terminal'; +import { Terminal } from 'browser/Terminal'; perfContext('Terminal: ls -lR /usr/lib', () => { let content = ''; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 3487c410..35f43b1b 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -805,7 +805,7 @@ declare module 'xterm' { /** * @deprecated use `registerMarker` instead. */ - addMarker(cursorYOffset: number): IMarker; + addMarker(cursorYOffset: number): IMarker | undefined; /** * Gets whether the terminal has an active selection. From 03bfd2c842d9947544e19a35e3622ff6771033fc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 10:59:32 -0700 Subject: [PATCH 33/35] Move Terminal*.test into browser --- bin/test.js | 1 - src/TestUtils.test.ts | 326 ---------------------------- src/{ => browser}/Terminal.test.ts | 288 ++++++++++++------------ src/{ => browser}/Terminal2.test.ts | 6 +- src/browser/TestUtils.test.ts | 324 ++++++++++++++++++++++++++- 5 files changed, 468 insertions(+), 477 deletions(-) delete mode 100644 src/TestUtils.test.ts rename src/{ => browser}/Terminal.test.ts (80%) rename src/{ => browser}/Terminal2.test.ts (96%) diff --git a/bin/test.js b/bin/test.js index 1380a512..b9725acd 100644 --- a/bin/test.js +++ b/bin/test.js @@ -13,7 +13,6 @@ const env = { ...process.env }; env.NODE_PATH = path.resolve(__dirname, '../out'); let testFiles = [ - './out/*test.js', './out/**/*test.js', './addons/**/out/*test.js', ]; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts deleted file mode 100644 index 102a5353..00000000 --- a/src/TestUtils.test.ts +++ /dev/null @@ -1,326 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; -import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; -import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; -import { Buffer } from 'common/buffer/Buffer'; -import * as Browser from 'common/Platform'; -import { IDisposable, IMarker, IEvent, ISelectionPosition, ILinkProvider } from 'xterm'; -import { Terminal } from './browser/Terminal'; -import { AttributeData } from 'common/buffer/AttributeData'; -import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport, ILinkifier2, ICompositionHelper, ITerminal, IBrowser } from 'browser/Types'; -import { IOptionsService, IUnicodeService } from 'common/services/Services'; -import { IParams, IFunctionIdentifier } from 'common/parser/Types'; -import { ISelectionService } from 'browser/services/Services'; - -export class TestTerminal extends Terminal { - public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } - public keyDown(ev: any): boolean { return this._keyDown(ev); } - public keyPress(ev: any): boolean { return this._keyPress(ev); } -} - -export class MockTerminal implements ITerminal { - public onBlur: IEvent; - public onFocus: IEvent; - public onA11yChar: IEvent; - public onA11yTab: IEvent; - public onCursorMove: IEvent; - public onLineFeed: IEvent; - public onSelectionChange: IEvent; - public onData: IEvent; - public onBinary: IEvent; - public onTitleChange: IEvent; - public onScroll: IEvent; - public onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; - public onRender: IEvent<{ start: number, end: number }>; - public onResize: IEvent<{ cols: number, rows: number }>; - public markers: IMarker[]; - public optionsService: IOptionsService; - public unicodeService: IUnicodeService; - public addMarker(cursorYOffset: number): IMarker { - throw new Error('Method not implemented.'); - } - public selectLines(start: number, end: number): void { - throw new Error('Method not implemented.'); - } - public scrollToLine(line: number): void { - throw new Error('Method not implemented.'); - } - public static string: any; - public setOption(key: any, value: any): void { - throw new Error('Method not implemented.'); - } - public blur(): void { - throw new Error('Method not implemented.'); - } - public focus(): void { - throw new Error('Method not implemented.'); - } - public resize(columns: number, rows: number): void { - throw new Error('Method not implemented.'); - } - public writeln(data: string): void { - throw new Error('Method not implemented.'); - } - public paste(data: string): void { - throw new Error('Method not implemented.'); - } - public open(parent: HTMLElement): void { - throw new Error('Method not implemented.'); - } - public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { - throw new Error('Method not implemented.'); - } - public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { - throw new Error('Method not implemented.'); - } - public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { - throw new Error('Method not implemented.'); - } - public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { - throw new Error('Method not implemented.'); - } - public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { - throw new Error('Method not implemented.'); - } - public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number { - throw new Error('Method not implemented.'); - } - public deregisterLinkMatcher(matcherId: number): void { - throw new Error('Method not implemented.'); - } - public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { - throw new Error('Method not implemented.'); - } - public hasSelection(): boolean { - throw new Error('Method not implemented.'); - } - public getSelection(): string { - throw new Error('Method not implemented.'); - } - public getSelectionPosition(): ISelectionPosition | undefined { - throw new Error('Method not implemented.'); - } - public clearSelection(): void { - throw new Error('Method not implemented.'); - } - public select(column: number, row: number, length: number): void { - throw new Error('Method not implemented.'); - } - public selectAll(): void { - throw new Error('Method not implemented.'); - } - public dispose(): void { - throw new Error('Method not implemented.'); - } - public scrollPages(pageCount: number): void { - throw new Error('Method not implemented.'); - } - public scrollToTop(): void { - throw new Error('Method not implemented.'); - } - public scrollToBottom(): void { - throw new Error('Method not implemented.'); - } - public clear(): void { - throw new Error('Method not implemented.'); - } - public write(data: string): void { - throw new Error('Method not implemented.'); - } - public writeUtf8(data: Uint8Array): void { - throw new Error('Method not implemented.'); - } - public bracketedPasteMode: boolean; - public renderer: IRenderer; - public linkifier: ILinkifier; - public linkifier2: ILinkifier2; - public isFocused: boolean; - public options: ITerminalOptions = {}; - public element: HTMLElement; - public screenElement: HTMLElement; - public rowContainer: HTMLElement; - public selectionContainer: HTMLElement; - public selectionService: ISelectionService; - public textarea: HTMLTextAreaElement; - public rows: number; - public cols: number; - public browser: IBrowser = Browser; - public writeBuffer: string[]; - public children: HTMLElement[]; - public cursorHidden: boolean; - public cursorState: number; - public scrollback: number; - public buffers: IBufferSet; - public buffer: IBuffer; - public viewport: IViewport; - public applicationCursor: boolean; - public handler(data: string): void { - throw new Error('Method not implemented.'); - } - public on(event: string, callback: (...args: any[]) => void): void { - throw new Error('Method not implemented.'); - } - public off(type: string, listener: XtermListener): void { - throw new Error('Method not implemented.'); - } - public addDisposableListener(type: string, handler: XtermListener): IDisposable { - throw new Error('Method not implemented.'); - } - public scrollLines(disp: number): void { - throw new Error('Method not implemented.'); - } - public scrollToRow(absoluteRow: number): number { - throw new Error('Method not implemented.'); - } - public cancel(ev: Event, force?: boolean): void { - throw new Error('Method not implemented.'); - } - public log(text: string): void { - throw new Error('Method not implemented.'); - } - public emit(event: string, data: any): void { - throw new Error('Method not implemented.'); - } - public reset(): void { - throw new Error('Method not implemented.'); - } - public refresh(start: number, end: number): void { - throw new Error('Method not implemented.'); - } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } - public deregisterCharacterJoiner(joinerId: number): void { } -} - -export class MockBuffer implements IBuffer { - public markers: IMarker[]; - public addMarker(y: number): IMarker { - throw new Error('Method not implemented.'); - } - public isCursorInViewport: boolean; - public lines: ICircularList; - public ydisp: number; - public ybase: number; - public hasScrollback: boolean; - public y: number; - public x: number; - public tabs: any; - public scrollBottom: number; - public scrollTop: number; - public savedY: number; - public savedX: number; - public savedCharset: ICharset | null; - public savedCurAttrData = new AttributeData(); - public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { - return Buffer.prototype.translateBufferLineToString.apply(this, arguments); - } - public getWrappedRangeForLine(y: number): { first: number, last: number } { - return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments); - } - public nextStop(x?: number): number { - throw new Error('Method not implemented.'); - } - public prevStop(x?: number): number { - throw new Error('Method not implemented.'); - } - public setLines(lines: ICircularList): void { - this.lines = lines; - } - public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { - return Buffer.prototype.getBlankLine.apply(this, arguments); - } - public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] { - return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments); - } - public iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator { - return Buffer.prototype.iterator.apply(this, arguments); - } - public getNullCell(attr?: IAttributeData): ICellData { - throw new Error('Method not implemented.'); - } - public getWhitespaceCell(attr?: IAttributeData): ICellData { - throw new Error('Method not implemented.'); - } -} - -export class MockRenderer implements IRenderer { - public onRequestRedraw: IEvent; - public onCanvasResize: IEvent<{ width: number, height: number }>; - public onRender: IEvent<{ start: number, end: number }>; - public dispose(): void { - throw new Error('Method not implemented.'); - } - public colorManager: IColorManager; - public on(type: string, listener: XtermListener): void { - throw new Error('Method not implemented.'); - } - public off(type: string, listener: XtermListener): void { - throw new Error('Method not implemented.'); - } - public emit(type: string, data?: any): void { - throw new Error('Method not implemented.'); - } - public addDisposableListener(type: string, handler: XtermListener): IDisposable { - throw new Error('Method not implemented.'); - } - public dimensions: IRenderDimensions; - public setColors(colors: IColorSet): void { - throw new Error('Method not implemented.'); - } - public onResize(cols: number, rows: number): void { } - public onCharSizeChanged(): void { } - public onBlur(): void { } - public onFocus(): void { } - public onSelectionChanged(start: [number, number], end: [number, number]): void { } - public onCursorMove(): void { } - public onOptionsChanged(): void { } - public onDevicePixelRatioChange(): void { } - public clear(): void { } - public renderRows(start: number, end: number): void { } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } - public deregisterCharacterJoiner(): boolean { return true; } -} - -export class MockViewport implements IViewport { - public dispose(): void { - throw new Error('Method not implemented.'); - } - public scrollBarWidth: number = 0; - public onThemeChange(colors: IColorSet): void { - throw new Error('Method not implemented.'); - } - public onWheel(ev: WheelEvent): boolean { - throw new Error('Method not implemented.'); - } - public onTouchStart(ev: TouchEvent): void { - throw new Error('Method not implemented.'); - } - public onTouchMove(ev: TouchEvent): boolean { - throw new Error('Method not implemented.'); - } - public syncScrollArea(): void { } - public getLinesScrolled(ev: WheelEvent): number { - throw new Error('Method not implemented.'); - } -} - -export class MockCompositionHelper implements ICompositionHelper { - public compositionstart(): void { - throw new Error('Method not implemented.'); - } - public compositionupdate(ev: CompositionEvent): void { - throw new Error('Method not implemented.'); - } - public compositionend(): void { - throw new Error('Method not implemented.'); - } - public updateCompositionElements(dontRecurse?: boolean): void { - throw new Error('Method not implemented.'); - } - public keydown(ev: KeyboardEvent): boolean { - return true; - } -} diff --git a/src/Terminal.test.ts b/src/browser/Terminal.test.ts similarity index 80% rename from src/Terminal.test.ts rename to src/browser/Terminal.test.ts index 6488aa43..729c0d0f 100644 --- a/src/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -4,7 +4,7 @@ */ import { assert, expect } from 'chai'; -import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from './TestUtils.test'; +import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test'; import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IUnicodeService } from 'common/services/Services'; @@ -395,62 +395,62 @@ describe('Terminal', () => { describe('scroll() function', () => { describe('when scrollback > 0', () => { it('should create a new line and scroll', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b'); - assert.equal(term.buffer.lines.get(INIT_ROWS).loadCell(0, new CellData()).getChars(), ''); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(INIT_ROWS)!.loadCell(0, new CellData()).getChars(), ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS + 1); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'c'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), 'd'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(5).loadCell(0, new CellData()).getChars(), 'e'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(5)!.loadCell(0, new CellData()).getChars(), 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e'); }); }); @@ -461,65 +461,65 @@ describe('Terminal', () => { }); it('should create a new line and shift everything up', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(INIT_ROWS - 1)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line assert.equal(term.buffer.lines.length, INIT_ROWS); term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); // 'a' gets pushed out of buffer - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), ''); - assert.equal(term.buffer.lines.get(INIT_ROWS - 2).loadCell(0, new CellData()).getChars(), 'c'); - assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), ''); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), ''); + assert.equal(term.buffer.lines.get(INIT_ROWS - 2)!.loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(INIT_ROWS - 1)!.loadCell(0, new CellData()).getChars(), ''); }); it('should properly scroll inside a scroll region (scrollTop set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c'); }); it('should properly scroll inside a scroll region (scrollBottom set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = 3; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'b'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c'); + assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e'); }); it('should properly scroll inside a scroll region (scrollTop and scrollBottom set)', () => { - term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); - term.buffer.lines.get(1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); - term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); - term.buffer.lines.get(3).setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); - term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); + term.buffer.lines.get(0)!.setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)])); + term.buffer.lines.get(1)!.setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)])); + term.buffer.lines.get(2)!.setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)])); + term.buffer.lines.get(3)!.setCell(0, CellData.fromCharData([0, 'd', 0, 'd'.charCodeAt(0)])); + term.buffer.lines.get(4)!.setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)])); term.buffer.y = INIT_ROWS - 1; // Move cursor to last line term.buffer.scrollTop = 1; term.buffer.scrollBottom = 3; term.scroll(DEFAULT_ATTR_DATA.clone()); assert.equal(term.buffer.lines.length, INIT_ROWS); - assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a'); - assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); - assert.equal(term.buffer.lines.get(2).loadCell(0, new CellData()).getChars(), 'd'); - assert.equal(term.buffer.lines.get(3).loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); - assert.equal(term.buffer.lines.get(4).loadCell(0, new CellData()).getChars(), 'e'); + assert.equal(term.buffer.lines.get(0)!.loadCell(0, new CellData()).getChars(), 'a'); + assert.equal(term.buffer.lines.get(1)!.loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer'); + assert.equal(term.buffer.lines.get(2)!.loadCell(0, new CellData()).getChars(), 'd'); + assert.equal(term.buffer.lines.get(3)!.loadCell(0, new CellData()).getChars(), '', 'a blank line should be added at scrollBottom\'s index'); + assert.equal(term.buffer.lines.get(4)!.loadCell(0, new CellData()).getChars(), 'e'); }); }); }); @@ -722,11 +722,11 @@ describe('Terminal', () => { const cell = new CellData(); for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.writeSync(high + String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0).loadCell(0, cell); + const tchar = term.buffer.lines.get(0)!.loadCell(0, cell); expect(tchar.getChars()).eql(high + String.fromCharCode(i)); expect(tchar.getChars().length).eql(2); expect(tchar.getWidth()).eql(1); - expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -736,9 +736,9 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.buffer.x = term.cols - 1; term.writeSync(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.buffer.x - 1, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length).eql(2); + expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(''); term.reset(); } }); @@ -749,10 +749,10 @@ describe('Terminal', () => { term.buffer.x = term.cols - 1; term.writeSync('a' + high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(1).loadCell(0, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length).eql(2); + expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -768,9 +768,9 @@ describe('Terminal', () => { } term.writeSync('a' + high + String.fromCharCode(i)); // auto wraparound mode should cut off the rest of the line - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i)); - expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars().length).eql(2); - expect(term.buffer.lines.get(1).loadCell(1, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i)); + expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length).eql(2); + expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -780,11 +780,11 @@ describe('Terminal', () => { for (let i = 0xDC00; i <= 0xDCFF; ++i) { term.writeSync(high); term.writeSync(String.fromCharCode(i)); - const tchar = term.buffer.lines.get(0).loadCell(0, cell); + const tchar = term.buffer.lines.get(0)!.loadCell(0, cell); expect(tchar.getChars()).eql(high + String.fromCharCode(i)); expect(tchar.getChars().length).eql(2); expect(tchar.getWidth()).eql(1); - expect(term.buffer.lines.get(0).loadCell(1, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql(''); term.reset(); } }); @@ -794,7 +794,7 @@ describe('Terminal', () => { const cell = new CellData(); it('café', () => { term.writeSync('cafe\u0301'); - term.buffer.lines.get(0).loadCell(3, cell); + term.buffer.lines.get(0)!.loadCell(3, cell); expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(1); @@ -802,11 +802,11 @@ describe('Terminal', () => { it('café - end of line', () => { term.buffer.x = term.cols - 1 - 3; term.writeSync('cafe\u0301'); - term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(1); - term.buffer.lines.get(0).loadCell(1, cell); + term.buffer.lines.get(0)!.loadCell(1, cell); expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); expect(cell.getWidth()).eql(1); @@ -814,12 +814,12 @@ describe('Terminal', () => { it('multiple combined é', () => { term.writeSync(Array(100).join('e\u0301')); for (let i = 0; i < term.cols; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(1); } - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('e\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(1); @@ -827,12 +827,12 @@ describe('Terminal', () => { it('multiple surrogate with combined', () => { term.writeSync(Array(100).join('\uD800\uDC00\u0301')); for (let i = 0; i < term.cols; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); expect(cell.getChars()).eql('\uD800\uDC00\u0301'); expect(cell.getChars().length).eql(3); expect(cell.getWidth()).eql(1); } - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('\uD800\uDC00\u0301'); expect(cell.getChars().length).eql(3); expect(cell.getWidth()).eql(1); @@ -855,7 +855,7 @@ describe('Terminal', () => { it('line of ¥ even', () => { term.writeSync(Array(50).join('¥')); for (let i = 0; i < term.cols; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -866,7 +866,7 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('¥'); expect(cell.getChars().length).eql(1); expect(cell.getWidth()).eql(2); @@ -875,7 +875,7 @@ describe('Terminal', () => { term.buffer.x = 1; term.writeSync(Array(50).join('¥')); for (let i = 1; i < term.cols - 1; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -886,11 +886,11 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); expect(cell.getWidth()).eql(1); - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('¥'); expect(cell.getChars().length).eql(1); expect(cell.getWidth()).eql(2); @@ -899,7 +899,7 @@ describe('Terminal', () => { term.buffer.x = 1; term.writeSync(Array(50).join('¥\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -910,11 +910,11 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); expect(cell.getWidth()).eql(1); - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('¥\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(2); @@ -922,7 +922,7 @@ describe('Terminal', () => { it('line of ¥ with combining even', () => { term.writeSync(Array(50).join('¥\u0301')); for (let i = 0; i < term.cols; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -933,7 +933,7 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('¥\u0301'); expect(cell.getChars().length).eql(2); expect(cell.getWidth()).eql(2); @@ -942,7 +942,7 @@ describe('Terminal', () => { term.buffer.x = 1; term.writeSync(Array(50).join('\ud843\ude6d\u0301')); for (let i = 1; i < term.cols - 1; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (!(i % 2)) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -953,11 +953,11 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(0).loadCell(term.cols - 1, cell); + term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell); expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); expect(cell.getWidth()).eql(1); - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('\ud843\ude6d\u0301'); expect(cell.getChars().length).eql(3); expect(cell.getWidth()).eql(2); @@ -965,7 +965,7 @@ describe('Terminal', () => { it('line of surrogate fullwidth with combining even', () => { term.writeSync(Array(50).join('\ud843\ude6d\u0301')); for (let i = 0; i < term.cols; ++i) { - term.buffer.lines.get(0).loadCell(i, cell); + term.buffer.lines.get(0)!.loadCell(i, cell); if (i % 2) { expect(cell.getChars()).eql(''); expect(cell.getChars().length).eql(0); @@ -976,7 +976,7 @@ describe('Terminal', () => { expect(cell.getWidth()).eql(2); } } - term.buffer.lines.get(1).loadCell(0, cell); + term.buffer.lines.get(1)!.loadCell(0, cell); expect(cell.getChars()).eql('\ud843\ude6d\u0301'); expect(cell.getChars().length).eql(3); expect(cell.getWidth()).eql(2); @@ -991,11 +991,11 @@ describe('Terminal', () => { term.buffer.y = 0; term.write('\x1b[4h'); term.writeSync('abcde'); - expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('e'); - expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql('0'); - expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('4'); + expect(term.buffer.lines.get(0)!.length).eql(term.cols); + expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('e'); + expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('0'); + expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('4'); }); it('fullwidth - insert', () => { term.writeSync(Array(9).join('0123456789').slice(-80)); @@ -1003,12 +1003,12 @@ describe('Terminal', () => { term.buffer.y = 0; term.write('\x1b[4h'); term.writeSync('¥¥¥'); - expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql(''); - expect(term.buffer.lines.get(0).loadCell(14, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(15, cell).getChars()).eql(''); - expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql('3'); + expect(term.buffer.lines.get(0)!.length).eql(term.cols); + expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql(''); + expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('3'); }); it('fullwidth - right border', () => { term.writeSync(Array(41).join('¥')); @@ -1016,15 +1016,15 @@ describe('Terminal', () => { term.buffer.y = 0; term.write('\x1b[4h'); term.writeSync('a'); - expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(10, cell).getChars()).eql('a'); - expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced + expect(term.buffer.lines.get(0)!.length).eql(term.cols); + expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a'); + expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced term.writeSync('b'); - expect(term.buffer.lines.get(0).length).eql(term.cols); - expect(term.buffer.lines.get(0).loadCell(11, cell).getChars()).eql('b'); - expect(term.buffer.lines.get(0).loadCell(12, cell).getChars()).eql('¥'); - expect(term.buffer.lines.get(0).loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth + expect(term.buffer.lines.get(0)!.length).eql(term.cols); + expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('b'); + expect(term.buffer.lines.get(0)!.loadCell(12, cell).getChars()).eql('¥'); + expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth }); }); @@ -1269,9 +1269,9 @@ describe('Terminal', () => { terminal.writeSync(input); const s = terminal.buffer.iterator(true).next().content; assert.equal(input, s); - const stringIndex = s.match(/😃/).index; + const stringIndex = s.match(/😃/)!.index!; const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); - assert(terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); + assert(terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); }); it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', () => { @@ -1298,7 +1298,7 @@ describe('Terminal', () => { assert.equal(input, s); for (let i = 0; i < input.length; ++i) { const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); + assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars()); } }); @@ -1316,7 +1316,7 @@ describe('Terminal', () => { : (i % 3 === 1) ? input.substr(i, 2) : input.substr(i - 1, 2), - terminal.buffer.lines.get(bufferIndex[0]).loadCell(bufferIndex[1], new CellData()).getChars()); + terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars()); } }); @@ -1370,15 +1370,15 @@ describe('Terminal', () => { const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); normalTerminal.writeSync(data.join('')); - assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false); - assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false); - assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false); const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); windowsModeTerminal.writeSync(data.join('')); - assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false); - assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); - assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); + assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false); }); it('should mark lines as wrapped when the line ends in a non-null character after a CUP', () => { @@ -1390,39 +1390,39 @@ describe('Terminal', () => { const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false}); normalTerminal.writeSync(data.join('')); - assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false); - assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false); - assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(0)!.isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(1)!.isWrapped, false); + assert.equal(normalTerminal.buffer.lines.get(2)!.isWrapped, false); const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true}); windowsModeTerminal.writeSync(data.join('')); - assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false); - assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); - assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(0)!.isWrapped, false); + assert.equal(windowsModeTerminal.buffer.lines.get(1)!.isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character'); + assert.equal(windowsModeTerminal.buffer.lines.get(2)!.isWrapped, false); }); }); it('convertEol setting', function(): void { // not converting const termNotConverting = new TestTerminal({cols: 15, rows: 10}); termNotConverting.writeSync('Hello\nWorld'); - expect(termNotConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termNotConverting.buffer.lines.get(1).translateToString(false)).equals(' World '); - expect(termNotConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termNotConverting.buffer.lines.get(1).translateToString(true)).equals(' World'); + expect(termNotConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello '); + expect(termNotConverting.buffer.lines.get(1)!.translateToString(false)).equals(' World '); + expect(termNotConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello'); + expect(termNotConverting.buffer.lines.get(1)!.translateToString(true)).equals(' World'); // converting const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true}); termConverting.writeSync('Hello\nWorld'); - expect(termConverting.buffer.lines.get(0).translateToString(false)).equals('Hello '); - expect(termConverting.buffer.lines.get(1).translateToString(false)).equals('World '); - expect(termConverting.buffer.lines.get(0).translateToString(true)).equals('Hello'); - expect(termConverting.buffer.lines.get(1).translateToString(true)).equals('World'); + expect(termConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello '); + expect(termConverting.buffer.lines.get(1)!.translateToString(false)).equals('World '); + expect(termConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello'); + expect(termConverting.buffer.lines.get(1)!.translateToString(true)).equals('World'); }); describe('Terminal InputHandler integration', () => { function getLines(term: TestTerminal, limit: number = term.rows): string[] { const res: string[] = []; for (let i = 0; i < limit; ++i) { - res.push(term.buffer.lines.get(i).translateToString(true)); + res.push(term.buffer.lines.get(i)!.translateToString(true)); } return res; } diff --git a/src/Terminal2.test.ts b/src/browser/Terminal2.test.ts similarity index 96% rename from src/Terminal2.test.ts rename to src/browser/Terminal2.test.ts index 720ea6b1..32a47b21 100644 --- a/src/Terminal2.test.ts +++ b/src/browser/Terminal2.test.ts @@ -8,14 +8,14 @@ import * as path from 'path'; import * as os from 'os'; import * as fs from 'fs'; import * as pty from 'node-pty'; -import { Terminal } from './browser/Terminal'; +import { Terminal } from 'browser/Terminal'; import { IDisposable } from 'xterm'; // all test files expect terminal in 80x25 const COLS = 80; const ROWS = 25; -const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '..')}); +const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')}); const SKIP_FILES = [ 't0084-CBT.in', 't0101-NLM.in', @@ -124,7 +124,7 @@ function terminalToString(term: Terminal): string { let result = ''; let lineText = ''; for (let line = term.buffer.ybase; line < term.buffer.ybase + term.rows; line++) { - lineText = term.buffer.lines.get(line).translateToString(true); + lineText = term.buffer.lines.get(line)!.translateToString(true); // rtrim empty cells as xterm does lineText = lineText.replace(/\s+$/, ''); result += lineText; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 1869ab87..2d6f6cbb 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -3,10 +3,328 @@ * @license MIT */ +import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; -import { ICharSizeService, IMouseService, IRenderService } from 'browser/services/Services'; -import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/renderer/Types'; -import { IColorSet } from 'browser/Types'; +import { ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; +import { IRenderDimensions, IRenderer, CharacterJoinerHandler, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper } from 'browser/Types'; +import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; +import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; +import { Buffer } from 'common/buffer/Buffer'; +import * as Browser from 'common/Platform'; +import { Terminal } from 'browser/Terminal'; +import { IUnicodeService, IOptionsService } from 'common/services/Services'; +import { IFunctionIdentifier, IParams } from 'common/parser/Types'; +import { AttributeData } from 'common/buffer/AttributeData'; + +export class TestTerminal extends Terminal { + public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; } + public keyDown(ev: any): boolean | undefined { return this._keyDown(ev); } + public keyPress(ev: any): boolean { return this._keyPress(ev); } +} + +export class MockTerminal implements ITerminal { + public onBlur!: IEvent; + public onFocus!: IEvent; + public onA11yChar!: IEvent; + public onA11yTab!: IEvent; + public onCursorMove!: IEvent; + public onLineFeed!: IEvent; + public onSelectionChange!: IEvent; + public onData!: IEvent; + public onBinary!: IEvent; + public onTitleChange!: IEvent; + public onScroll!: IEvent; + public onKey!: IEvent<{ key: string, domEvent: KeyboardEvent }>; + public onRender!: IEvent<{ start: number, end: number }>; + public onResize!: IEvent<{ cols: number, rows: number }>; + public markers!: IMarker[]; + public optionsService!: IOptionsService; + public unicodeService!: IUnicodeService; + public addMarker(cursorYOffset: number): IMarker { + throw new Error('Method not implemented.'); + } + public selectLines(start: number, end: number): void { + throw new Error('Method not implemented.'); + } + public scrollToLine(line: number): void { + throw new Error('Method not implemented.'); + } + public static string: any; + public setOption(key: any, value: any): void { + throw new Error('Method not implemented.'); + } + public blur(): void { + throw new Error('Method not implemented.'); + } + public focus(): void { + throw new Error('Method not implemented.'); + } + public resize(columns: number, rows: number): void { + throw new Error('Method not implemented.'); + } + public writeln(data: string): void { + throw new Error('Method not implemented.'); + } + public paste(data: string): void { + throw new Error('Method not implemented.'); + } + public open(parent: HTMLElement): void { + throw new Error('Method not implemented.'); + } + public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void { + throw new Error('Method not implemented.'); + } + public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable { + throw new Error('Method not implemented.'); + } + public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable { + throw new Error('Method not implemented.'); + } + public addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable { + throw new Error('Method not implemented.'); + } + public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable { + throw new Error('Method not implemented.'); + } + public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number { + throw new Error('Method not implemented.'); + } + public deregisterLinkMatcher(matcherId: number): void { + throw new Error('Method not implemented.'); + } + public registerLinkProvider(linkProvider: ILinkProvider): IDisposable { + throw new Error('Method not implemented.'); + } + public hasSelection(): boolean { + throw new Error('Method not implemented.'); + } + public getSelection(): string { + throw new Error('Method not implemented.'); + } + public getSelectionPosition(): ISelectionPosition | undefined { + throw new Error('Method not implemented.'); + } + public clearSelection(): void { + throw new Error('Method not implemented.'); + } + public select(column: number, row: number, length: number): void { + throw new Error('Method not implemented.'); + } + public selectAll(): void { + throw new Error('Method not implemented.'); + } + public dispose(): void { + throw new Error('Method not implemented.'); + } + public scrollPages(pageCount: number): void { + throw new Error('Method not implemented.'); + } + public scrollToTop(): void { + throw new Error('Method not implemented.'); + } + public scrollToBottom(): void { + throw new Error('Method not implemented.'); + } + public clear(): void { + throw new Error('Method not implemented.'); + } + public write(data: string): void { + throw new Error('Method not implemented.'); + } + public writeUtf8(data: Uint8Array): void { + throw new Error('Method not implemented.'); + } + public bracketedPasteMode!: boolean; + public renderer!: IRenderer; + public linkifier!: ILinkifier; + public linkifier2!: ILinkifier2; + public isFocused!: boolean; + public options: ITerminalOptions = {}; + public element!: HTMLElement; + public screenElement!: HTMLElement; + public rowContainer!: HTMLElement; + public selectionContainer!: HTMLElement; + public selectionService!: ISelectionService; + public textarea!: HTMLTextAreaElement; + public rows!: number; + public cols!: number; + public browser: IBrowser = Browser; + public writeBuffer!: string[]; + public children!: HTMLElement[]; + public cursorHidden!: boolean; + public cursorState!: number; + public scrollback!: number; + public buffers!: IBufferSet; + public buffer!: IBuffer; + public viewport!: IViewport; + public applicationCursor!: boolean; + public handler(data: string): void { + throw new Error('Method not implemented.'); + } + public on(event: string, callback: (...args: any[]) => void): void { + throw new Error('Method not implemented.'); + } + public off(type: string, listener: XtermListener): void { + throw new Error('Method not implemented.'); + } + public addDisposableListener(type: string, handler: XtermListener): IDisposable { + throw new Error('Method not implemented.'); + } + public scrollLines(disp: number): void { + throw new Error('Method not implemented.'); + } + public scrollToRow(absoluteRow: number): number { + throw new Error('Method not implemented.'); + } + public cancel(ev: Event, force?: boolean): void { + throw new Error('Method not implemented.'); + } + public log(text: string): void { + throw new Error('Method not implemented.'); + } + public emit(event: string, data: any): void { + throw new Error('Method not implemented.'); + } + public reset(): void { + throw new Error('Method not implemented.'); + } + public refresh(start: number, end: number): void { + throw new Error('Method not implemented.'); + } + public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } + public deregisterCharacterJoiner(joinerId: number): void { } +} + +export class MockBuffer implements IBuffer { + public markers!: IMarker[]; + public addMarker(y: number): IMarker { + throw new Error('Method not implemented.'); + } + public isCursorInViewport!: boolean; + public lines!: ICircularList; + public ydisp!: number; + public ybase!: number; + public hasScrollback!: boolean; + public y!: number; + public x!: number; + public tabs: any; + public scrollBottom!: number; + public scrollTop!: number; + public savedY!: number; + public savedX!: number; + public savedCharset: ICharset | undefined; + public savedCurAttrData = new AttributeData(); + public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string { + return Buffer.prototype.translateBufferLineToString.apply(this, arguments as any); + } + public getWrappedRangeForLine(y: number): { first: number, last: number } { + return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments as any); + } + public nextStop(x?: number): number { + throw new Error('Method not implemented.'); + } + public prevStop(x?: number): number { + throw new Error('Method not implemented.'); + } + public setLines(lines: ICircularList): void { + this.lines = lines; + } + public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { + return Buffer.prototype.getBlankLine.apply(this, arguments as any); + } + public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] { + return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments as any); + } + public iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator { + return Buffer.prototype.iterator.apply(this, arguments as any); + } + public getNullCell(attr?: IAttributeData): ICellData { + throw new Error('Method not implemented.'); + } + public getWhitespaceCell(attr?: IAttributeData): ICellData { + throw new Error('Method not implemented.'); + } +} + +export class MockRenderer implements IRenderer { + public onRequestRedraw!: IEvent; + public onCanvasResize!: IEvent<{ width: number, height: number }>; + public onRender!: IEvent<{ start: number, end: number }>; + public dispose(): void { + throw new Error('Method not implemented.'); + } + public colorManager!: IColorManager; + public on(type: string, listener: XtermListener): void { + throw new Error('Method not implemented.'); + } + public off(type: string, listener: XtermListener): void { + throw new Error('Method not implemented.'); + } + public emit(type: string, data?: any): void { + throw new Error('Method not implemented.'); + } + public addDisposableListener(type: string, handler: XtermListener): IDisposable { + throw new Error('Method not implemented.'); + } + public dimensions!: IRenderDimensions; + public setColors(colors: IColorSet): void { + throw new Error('Method not implemented.'); + } + public onResize(cols: number, rows: number): void { } + public onCharSizeChanged(): void { } + public onBlur(): void { } + public onFocus(): void { } + public onSelectionChanged(start: [number, number], end: [number, number]): void { } + public onCursorMove(): void { } + public onOptionsChanged(): void { } + public onDevicePixelRatioChange(): void { } + public clear(): void { } + public renderRows(start: number, end: number): void { } + public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; } + public deregisterCharacterJoiner(): boolean { return true; } +} + +export class MockViewport implements IViewport { + public dispose(): void { + throw new Error('Method not implemented.'); + } + public scrollBarWidth: number = 0; + public onThemeChange(colors: IColorSet): void { + throw new Error('Method not implemented.'); + } + public onWheel(ev: WheelEvent): boolean { + throw new Error('Method not implemented.'); + } + public onTouchStart(ev: TouchEvent): void { + throw new Error('Method not implemented.'); + } + public onTouchMove(ev: TouchEvent): boolean { + throw new Error('Method not implemented.'); + } + public syncScrollArea(): void { } + public getLinesScrolled(ev: WheelEvent): number { + throw new Error('Method not implemented.'); + } +} + +export class MockCompositionHelper implements ICompositionHelper { + public compositionstart(): void { + throw new Error('Method not implemented.'); + } + public compositionupdate(ev: CompositionEvent): void { + throw new Error('Method not implemented.'); + } + public compositionend(): void { + throw new Error('Method not implemented.'); + } + public updateCompositionElements(dontRecurse?: boolean): void { + throw new Error('Method not implemented.'); + } + public keydown(ev: KeyboardEvent): boolean { + return true; + } +} export class MockCharSizeService implements ICharSizeService { public serviceBrand: undefined; From 80c5c94e7ea9a7ceba8cd0c1af37cd60e1e58dc3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 11:06:30 -0700 Subject: [PATCH 34/35] Remove src project --- .../benchmark/SerializeAddon.benchmark.ts | 2 +- .../benchmark/tsconfig.json | 2 -- demo/client.ts | 2 +- src/Types.d.ts | 15 -------- src/{ => browser}/public/AddonManager.test.ts | 6 ++-- src/{ => browser}/public/AddonManager.ts | 0 src/{ => browser}/public/Terminal.ts | 10 +++--- src/tsconfig.json | 34 ------------------- tsconfig.all.json | 2 +- typings/xterm.d.ts | 3 +- webpack.config.js | 2 +- 11 files changed, 14 insertions(+), 64 deletions(-) delete mode 100644 src/Types.d.ts rename src/{ => browser}/public/AddonManager.test.ts (90%) rename src/{ => browser}/public/AddonManager.ts (100%) rename src/{ => browser}/public/Terminal.ts (97%) delete mode 100644 src/tsconfig.json diff --git a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts index fbc16e03..c1764d8e 100644 --- a/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts +++ b/addons/xterm-addon-serialize/benchmark/SerializeAddon.benchmark.ts @@ -7,7 +7,7 @@ import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark'; import { spawn } from 'node-pty'; import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder'; -import { Terminal } from 'public/Terminal'; +import { Terminal } from 'browser/public/Terminal'; import { SerializeAddon } from 'SerializeAddon'; class TestTerminal extends Terminal { diff --git a/addons/xterm-addon-serialize/benchmark/tsconfig.json b/addons/xterm-addon-serialize/benchmark/tsconfig.json index 4e62ed59..42aaaa1b 100644 --- a/addons/xterm-addon-serialize/benchmark/tsconfig.json +++ b/addons/xterm-addon-serialize/benchmark/tsconfig.json @@ -11,8 +11,6 @@ "paths": { "common/*": ["../../../src/common/*"], "browser/*": ["../../../src/browser/*"], - "public/*": ["../../../src/public/*"], - "Terminal": ["../../../src/Terminal"], "SerializeAddon": ["../src/SerializeAddon"] } }, diff --git a/demo/client.ts b/demo/client.ts index b009a534..eaa6f8c1 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -8,7 +8,7 @@ /// // Use tsc version (yarn watch) -import { Terminal } from '../out/public/Terminal'; +import { Terminal } from '../out/browser/public/Terminal'; import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; diff --git a/src/Types.d.ts b/src/Types.d.ts deleted file mode 100644 index 1ffb7c5e..00000000 --- a/src/Types.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable, IMarker, ISelectionPosition, ILinkProvider } from 'xterm'; -import { IAttributeData, CharData, ITerminalOptions, ICoreTerminal } from 'common/Types'; -import { IEvent } from 'common/EventEmitter'; -import { ILinkifier, ILinkMatcherOptions, IViewport, ILinkifier2 } from 'browser/Types'; -import { IOptionsService, IUnicodeService } from 'common/services/Services'; -import { IBuffer, IBufferSet } from 'common/buffer/Types'; -import { IParams, IFunctionIdentifier } from 'common/parser/Types'; -import { Linkifier } from 'browser/Linkifier'; -import { Linkifier2 } from 'browser/Linkifier2'; - diff --git a/src/public/AddonManager.test.ts b/src/browser/public/AddonManager.test.ts similarity index 90% rename from src/public/AddonManager.test.ts rename to src/browser/public/AddonManager.test.ts index ea229095..e947976a 100644 --- a/src/public/AddonManager.test.ts +++ b/src/browser/public/AddonManager.test.ts @@ -42,9 +42,9 @@ describe('AddonManager', () => { public activate(): void {} public dispose(): void { called++; } } - manager.loadAddon(null, new Addon()); - manager.loadAddon(null, new Addon()); - manager.loadAddon(null, new Addon()); + manager.loadAddon(null!, new Addon()); + manager.loadAddon(null!, new Addon()); + manager.loadAddon(null!, new Addon()); assert.equal(manager.addons.length, 3); manager.dispose(); assert.equal(called, 3); diff --git a/src/public/AddonManager.ts b/src/browser/public/AddonManager.ts similarity index 100% rename from src/public/AddonManager.ts rename to src/browser/public/AddonManager.ts diff --git a/src/public/Terminal.ts b/src/browser/public/Terminal.ts similarity index 97% rename from src/public/Terminal.ts rename to src/browser/public/Terminal.ts index 3d483814..b49fc6b8 100644 --- a/src/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -8,8 +8,8 @@ import { ITerminal } from 'browser/Types'; import { IBufferLine, ICellData } from 'common/Types'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { CellData } from 'common/buffer/CellData'; -import { Terminal as TerminalCore } from '../browser/Terminal'; -import * as Strings from '../browser/LocalizableStrings'; +import { Terminal as TerminalCore } from '../Terminal'; +import * as Strings from '../LocalizableStrings'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; import { IParams } from 'common/parser/Types'; @@ -17,7 +17,7 @@ import { IParams } from 'common/parser/Types'; export class Terminal implements ITerminalApi { private _core: ITerminal; private _addonManager: AddonManager; - private _parser: IParser; + private _parser: IParser | undefined; constructor(options?: ITerminalOptions) { this._core = new TerminalCore(options); @@ -81,11 +81,11 @@ export class Terminal implements ITerminalApi { public deregisterCharacterJoiner(joinerId: number): void { this._core.deregisterCharacterJoiner(joinerId); } - public registerMarker(cursorYOffset: number): IMarker { + public registerMarker(cursorYOffset: number): IMarker | undefined { this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } - public addMarker(cursorYOffset: number): IMarker { + public addMarker(cursorYOffset: number): IMarker | undefined { return this.registerMarker(cursorYOffset); } public hasSelection(): boolean { diff --git a/src/tsconfig.json b/src/tsconfig.json deleted file mode 100644 index 97576668..00000000 --- a/src/tsconfig.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "extends": "./tsconfig-base", - "compilerOptions": { - "module": "commonjs", - "lib": [ - "dom", - "es5", - "es6", - "scripthost", - "es2015.promise" - ], - "rootDir": ".", - "outDir": "../out", - "baseUrl": ".", - "paths": { - "common/*": [ "./common/*" ], - "browser/*": [ "./browser/*" ] - }, - - "noUnusedLocals": true, - "noImplicitAny": true - }, - "include": [ - "./**/*", - "../typings/xterm.d.ts" - ], - "exclude": [ - "./addons/**/*" - ], - "references": [ - { "path": "./common" }, - { "path": "./browser" } - ] -} diff --git a/tsconfig.all.json b/tsconfig.all.json index 8143ea91..a8febb72 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -2,7 +2,7 @@ "files": [], "include": [], "references": [ - { "path": "./src" }, + { "path": "./src/browser" }, { "path": "./test/api" }, { "path": "./test/benchmark" }, { "path": "./addons/xterm-addon-attach/src" }, diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 35f43b1b..dfda8583 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -799,8 +799,9 @@ declare module 'xterm' { * (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the * alt buffer is active, undefined is returned. * @param cursorYOffset The y position offset of the marker from the cursor. + * @returns The new marker or undefined. */ - registerMarker(cursorYOffset: number): IMarker; + registerMarker(cursorYOffset: number): IMarker | undefined; /** * @deprecated use `registerMarker` instead. diff --git a/webpack.config.js b/webpack.config.js index a9a60241..4f9087b5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,7 +12,7 @@ const path = require('path'); * output by tsc (because of `baseUrl` and `paths` in `tsconfig.json`. */ module.exports = { - entry: './out/public/Terminal.js', + entry: './out/browser/public/Terminal.js', devtool: 'source-map', module: { rules: [ From 394bb38913c421ee1959a4add013b9d1df456bdc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 26 Apr 2020 11:11:04 -0700 Subject: [PATCH 35/35] Remove src/tsconfig.json from eslintrc --- .eslintrc.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 41767bed..32f8e992 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,6 @@ "project": [ "src/browser/tsconfig.json", "src/common/tsconfig.json", - "src/tsconfig.json", "test/api/tsconfig.json", "test/benchmark/tsconfig.json", "addons/xterm-addon-attach/src/tsconfig.json",