/** * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IMouseStateService, ICharsetService, UnicodeCharProperties, UnicodeCharWidth, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration, IOscLinkService, type IBufferResizeEvent } from 'common/services/Services'; import { UnicodeService } from 'common/services/UnicodeService'; 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, IModes, IAttributeData, IOscLinkData, IDisposable, IBufferLine, IExtendedAttrs } from 'common/Types'; import { UnicodeV6 } from 'common/input/UnicodeV6'; import { IDecorationOptions, IDecoration } from '@xterm/xterm'; import { Emitter, type IEvent } from 'common/Event'; import { CellData } from 'common/buffer/CellData'; import { DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH } from 'common/buffer/Constants'; export function createCellData(attr: number, char: string, width: number): CellData { return CellData.fromCharData([attr, char, width, char.length === 0 ? 0 : char.charCodeAt(0)]); } export function extendedAttributes(line: IBufferLine, index: number): IExtendedAttrs | undefined { const cell = new CellData(); line.loadCell(index, cell); return cell.hasExtendedAttrs() !== 0 ? cell.extended : undefined; } export const NULL_CELL_DATA = Object.freeze(createCellData(DEFAULT_ATTR, NULL_CELL_CHAR, NULL_CELL_WIDTH)); export class MockBufferService implements IBufferService { public serviceBrand: any; public get buffer(): IBuffer { return this.buffers.active; } public buffers: IBufferSet = {} as any; public onResize: IEvent = new Emitter().event; public onScroll: IEvent = new Emitter().event; private readonly _onScroll = new Emitter(); public isUserScrolling: boolean = false; constructor( public cols: number, public rows: number, optionsService: IOptionsService = new MockOptionsService() ) { this.buffers = new BufferSet(optionsService, this, new MockLogService()); // Listen to buffer activation events and automatically fire scroll events this.buffers.onBufferActivate(e => { this._onScroll.fire(e.activeBuffer.ydisp); }); } public scrollPages(pageCount: number): void { throw new Error('Method not implemented.'); } public scrollToTop(): void { throw new Error('Method not implemented.'); } public scrollToLine(line: number): void { throw new Error('Method not implemented.'); } public scroll(eraseAttr: IAttributeData, isWrapped: boolean): void { throw new Error('Method not implemented.'); } public scrollToBottom(): void { throw new Error('Method not implemented.'); } public scrollLines(disp: number, suppressScrollEvent?: boolean): void { throw new Error('Method not implemented.'); } public resize(cols: number, rows: number): void { this.cols = cols; this.rows = rows; } public reset(): void { } } export class MockMouseStateService implements IMouseStateService { public serviceBrand: any; public areMouseEventsActive: boolean = false; public activeEncoding: string = ''; public activeProtocol: string = ''; public isDefaultEncoding: boolean = true; public isPixelEncoding: boolean = false; public addEncoding(name: string): void { } public addProtocol(name: string): void { } public reset(): void { } public onProtocolChange: IEvent = new Emitter().event; public restrictMouseEvent(event: ICoreMouseEvent): boolean { return true; } public encodeMouseEvent(event: ICoreMouseEvent): string { return ''; } public setCustomWheelEventHandler(customWheelEventHandler: ((event: WheelEvent) => boolean) | undefined): void { } public allowCustomWheelEvent(ev: WheelEvent): boolean { return true; } } export class MockCharsetService implements ICharsetService { public serviceBrand: any; public charset: ICharset | undefined; public glevel: number = 0; public charsets: (ICharset | undefined)[] = []; public reset(): void { } public setgLevel(g: number): void { this.glevel = g; this.charset = this.charsets[g]; } public setgCharset(g: number, charset: ICharset | undefined): void { this.charsets[g] = charset; if (this.glevel === g) { this.charset = charset; } } } export class MockCoreService implements ICoreService { public serviceBrand: any; public isCursorInitialized: boolean = true; public isCursorHidden: boolean = false; public isFocused: boolean = false; public modes: IModes = { insertMode: false }; public decPrivateModes: IDecPrivateModes = { applicationCursorKeys: false, applicationKeypad: false, bracketedPasteMode: false, colorSchemeUpdates: false, cursorBlink: undefined, cursorStyle: undefined, origin: false, reverseWraparound: false, sendFocus: false, synchronizedOutput: false, win32InputMode: false, wraparound: true }; public kittyKeyboard = { flags: 0, mainFlags: 0, altFlags: 0, mainStack: [] as number[], altStack: [] as number[] }; public onData: IEvent = new Emitter().event; public onUserInput: IEvent = new Emitter().event; public onBinary: IEvent = new Emitter().event; public onRequestScrollToBottom: IEvent = new Emitter().event; public reset(): void { } public triggerDataEvent(data: string, wasUserInput?: boolean): void { } public triggerBinaryEvent(data: string): void { } } export class MockLogService implements ILogService { public serviceBrand: any; public logLevel = LogLevelEnum.DEBUG; public trace(message: any, ...optionalParams: any[]): void { } public debug(message: any, ...optionalParams: any[]): void { } public info(message: any, ...optionalParams: any[]): void { } public warn(message: any, ...optionalParams: any[]): void { } public error(message: any, ...optionalParams: any[]): void { } } export class MockOptionsService implements IOptionsService { public serviceBrand: any; public readonly rawOptions: Required = structuredClone(DEFAULT_OPTIONS); public options: Required = this.rawOptions; public onOptionChange: IEvent = new Emitter().event; constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { this.rawOptions[key] = testOptions[key]; } } } // eslint-disable-next-line @typescript-eslint/naming-convention public onSpecificOptionChange(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable { return this.onOptionChange(eventKey => { if (eventKey === key) { listener(this.rawOptions[key]); } }); } // eslint-disable-next-line @typescript-eslint/naming-convention public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable { return this.onOptionChange(eventKey => { if (keys.indexOf(eventKey) !== -1) { listener(); } }); } public setOptions(options: ITerminalOptions): void { for (const key of Object.keys(options)) { this.options[key] = options[key]; } } } export class MockOscLinkService implements IOscLinkService { public serviceBrand: any; public registerLink(linkData: IOscLinkData): number { return 1; } public getLinkData(linkId: number): IOscLinkData | undefined { return undefined; } public addLineToLink(linkId: number, y: number): void { } } // defaults to V6 always to keep tests passing export class MockUnicodeService implements IUnicodeService { public serviceBrand: any; private _provider = new UnicodeV6(); public register(provider: IUnicodeVersionProvider): void { throw new Error('Method not implemented.'); } public versions: string[] = []; public activeVersion: string = ''; public onChange: IEvent = new Emitter().event; public wcwidth = (codepoint: number): UnicodeCharWidth => this._provider.wcwidth(codepoint); public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties { let width = this.wcwidth(codepoint); let shouldJoin = width === 0 && preceding !== 0; if (shouldJoin) { const oldWidth = UnicodeService.extractWidth(preceding); if (oldWidth === 0) { shouldJoin = false; } else if (oldWidth > width) { width = oldWidth; } } return UnicodeService.createPropertyValue(0, width, shouldJoin); } public getStringCellWidth(s: string): number { throw new Error('Method not implemented.'); } } export class MockDecorationService implements IDecorationService { public serviceBrand: any; public get decorations(): IterableIterator { return [].values(); } public onDecorationRegistered = new Emitter().event; public onDecorationRemoved = new Emitter().event; public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; } public reset(): void { } public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { } public dispose(): void { } }