diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 59cc773a..c7c8438c 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -72,6 +72,9 @@ export class MockTerminal implements ITerminal { public focus(): void { throw new Error('Method not implemented.'); } + public input(data: string, wasUserInput: boolean = true): void { + throw new Error('Method not implemented.'); + } public resize(columns: number, rows: number): void { throw new Error('Method not implemented.'); } diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index ade46fa4..a6349225 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -138,6 +138,9 @@ export class Terminal extends Disposable implements ITerminalApi { public focus(): void { this._core.focus(); } + public input(data: string, wasUserInput: boolean = true): void { + this._core.input(data, wasUserInput); + } public resize(columns: number, rows: number): void { this._verifyIntegers(columns, rows); this._core.resize(columns, rows); diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 50725108..6f61a704 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -92,7 +92,7 @@ export class CellColorResolver { $bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - $bg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + $bg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF; break; case Attributes.CM_DEFAULT: default: @@ -105,7 +105,7 @@ export class CellColorResolver { $bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - $bg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + $bg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF; break; // No need to consider default bg color here as it's not possible } @@ -143,7 +143,7 @@ export class CellColorResolver { $fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - $fg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + $fg = ((this.result.bg & Attributes.RGB_MASK) << 8) | 0xFF; break; // No need to consider default bg color here as it's not possible } @@ -154,7 +154,7 @@ export class CellColorResolver { $fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; break; case Attributes.CM_RGB: - $fg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + $fg = ((this.result.fg & Attributes.RGB_MASK) << 8) | 0xFF; break; case Attributes.CM_DEFAULT: default: diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index c2cb9a05..a184641b 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -247,7 +247,7 @@ export class RenderService extends Disposable implements IRenderService { return; } if (this._isPaused) { - this._pausedResizeTask.set(() => this._renderer.value!.handleResize(cols, rows)); + this._pausedResizeTask.set(() => this._renderer.value?.handleResize(cols, rows)); } else { this._renderer.value.handleResize(cols, rows); } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 1789daf8..327b8bc2 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -168,6 +168,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { this._writeBuffer.writeSync(data, maxSubsequentCalls); } + public input(data: string, wasUserInput: boolean = true): void { + this.coreService.triggerDataEvent(data, wasUserInput); + } + public resize(x: number, y: number): void { if (isNaN(x) || isNaN(y)) { return; diff --git a/src/headless/Terminal.ts b/src/headless/Terminal.ts index 18000c8f..66040756 100644 --- a/src/headless/Terminal.ts +++ b/src/headless/Terminal.ts @@ -81,6 +81,10 @@ export class Terminal extends CoreTerminal { this._onBell.fire(); } + public input(data: string, wasUserInput: boolean = true): void { + this.coreService.triggerDataEvent(data, wasUserInput); + } + /** * Resizes the terminal. * diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index df202660..0d73f9d3 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -134,6 +134,9 @@ export class Terminal extends Disposable implements ITerminalApi { this._publicOptions[propName] = options[propName]; } } + public input(data: string, wasUserInput: boolean = true): void { + this._core.input(data, wasUserInput); + } public resize(columns: number, rows: number): void { this._verifyIntegers(columns, rows); this._core.resize(columns, rows); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index 657fba44..29dc51c3 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -6,7 +6,7 @@ import { IImage32, decodePng } from '@lunapaint/png-codec'; import { LocatorScreenshotOptions, test } from '@playwright/test'; import { ITheme } from '@xterm/xterm'; -import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } from './TestUtils'; +import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate, timeout } from './TestUtils'; export interface ISharedRendererTestContext { value: ITestContext; @@ -989,13 +989,15 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }; await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); await ctx.value.proxy.focus(); - await ctx.value.proxy.writeln('\x1b[41m red bg'); - await ctx.value.proxy.writeln('\x1b[7m inverse'); - await ctx.value.proxy.writeln('\x1b[31;7m red fg inverse'); + await ctx.value.proxy.writeln('\x1b[41m red bg\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[7m inverse\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[31;7m red fg inverse\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[48:2:0:204:0:0m red truecolor bg\x1b[0m'); await ctx.value.proxy.selectAll(); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [230,128,128,255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [255,255,255,255]); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [230,128,128,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [230, 128, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [255, 255, 255, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [230, 128, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [230, 128, 128, 255]); }); test('powerline decorative symbols', async () => { const theme: ITheme = { diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 4d4112f0..1427578c 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -216,6 +216,7 @@ export class TerminalProxy implements ITerminalProxyCustomMethods, PlaywrightApi return new Promise(r => term.writeln(typeof data === 'string' ? data : new Uint8Array(data), r)); }, [await this.getHandle(), typeof data === 'string' ? data : Array.from(data)] as const); } + public async input(data: string, wasUserInput: boolean = true): Promise { return this.evaluate(([term]) => term.input(data, wasUserInput)); } public async resize(cols: number, rows: number): Promise { return this._page.evaluate(([term, cols, rows]) => term.resize(cols, rows), [await this.getHandle(), cols, rows] as const); } public async registerMarker(y?: number | undefined): Promise { return this._page.evaluate(([term, y]) => term.registerMarker(y), [await this.getHandle(), y] as const); } public async registerDecoration(decorationOptions: IDecorationOptions): Promise { return this._page.evaluate(([term, decorationOptions]) => term.registerDecoration(decorationOptions), [await this.getHandle(), decorationOptions] as const); } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index f8cef382..b2ad5745 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -718,6 +718,18 @@ declare module '@xterm/headless' { */ onTitleChange: IEvent; + /** + * Input data to application side. The data is treated the same way input + * typed into the terminal would (ie. the {@link onData} event will fire). + * @param data The data to forward to the application. + * @param wasUserInput Whether the input is genuine user input. This is true + * by default and triggers additionalbehavior like focus or selection + * clearing. Set this to false if the data sent should not be treated like + * user input would, for example passing an escape sequence to the + * application. + */ + input(data: string, wasUserInput?: boolean): void; + /** * Resizes the terminal. It's best practice to debounce calls to resize, * this will help ensure that the pty can respond to the resize event diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 39a9c91a..d957d655 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -963,6 +963,18 @@ declare module '@xterm/xterm' { */ focus(): void; + /** + * Input data to application side. The data is treated the same way input + * typed into the terminal would (ie. the {@link onData} event will fire). + * @param data The data to forward to the application. + * @param wasUserInput Whether the input is genuine user input. This is true + * by default and triggers additionalbehavior like focus or selection + * clearing. Set this to false if the data sent should not be treated like + * user input would, for example passing an escape sequence to the + * application. + */ + input(data: string, wasUserInput?: boolean): void; + /** * Resizes the terminal. It's best practice to debounce calls to resize, * this will help ensure that the pty can respond to the resize event