diff --git a/src/browser/CoreBrowserTerminal.ts b/src/browser/CoreBrowserTerminal.ts index 38ad467a..a1c27fee 100644 --- a/src/browser/CoreBrowserTerminal.ts +++ b/src/browser/CoreBrowserTerminal.ts @@ -126,8 +126,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal { public readonly onCursorMove = this._onCursorMove.event; private readonly _onKey = this._register(new Emitter<{ key: string, domEvent: KeyboardEvent }>()); public readonly onKey = this._onKey.event; - private readonly _onRender = this._register(new Emitter<{ start: number, end: number }>()); - public readonly onRender = this._onRender.event; private readonly _onSelectionChange = this._register(new Emitter()); public readonly onSelectionChange = this._onSelectionChange.event; private readonly _onTitleChange = this._register(new Emitter()); diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index d9c5e8d2..265d3b07 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -65,6 +65,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public readonly onData = this._onData.event; protected _onLineFeed = this._register(new Emitter()); public readonly onLineFeed = this._onLineFeed.event; + protected readonly _onRender = this._register(new Emitter<{ start: number, end: number }>()); + public readonly onRender = this._onRender.event; private readonly _onResize = this._register(new Emitter<{ cols: number, rows: number }>()); public readonly onResize = this._onResize.event; protected readonly _onWriteParsed = this._register(new Emitter()); @@ -124,6 +126,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { // Register input handler and handle/forward events this._inputHandler = this._register(new InputHandler(this._bufferService, this._charsetService, this.coreService, this._logService, this.optionsService, this._oscLinkService, this.coreMouseService, this.unicodeService)); this._register(Event.forward(this._inputHandler.onLineFeed, this._onLineFeed)); + this._register(Event.forward(Event.map(this._inputHandler.onRequestRefreshRows, e => ({ start: e?.start ?? 0, end: e?.end ?? this.rows - 1 })), this._onRender)); this._register(this._inputHandler); // Setup listeners diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index 289eb72d..1ddd8c05 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -202,6 +202,15 @@ describe('Headless API Tests', function (): void { strictEqual(callCount, 2); }); + it('onRender', async () => { + const calls: { start: number, end: number }[] = []; + term.onRender(e => calls.push(e)); + await writeSync('foo'); + deepStrictEqual(calls, [{ start: 0, end: 0 }]); + await writeSync('\n\nbar'); + deepStrictEqual(calls, [{ start: 0, end: 0 }, { start: 0, end: 2 }]); + }); + it('onScroll', async () => { term = new Terminal({ rows: 5 }); const calls: number[] = []; diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index c8d6fe7c..00acae8d 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -77,6 +77,7 @@ export class Terminal extends Disposable implements ITerminalApi { public get onCursorMove(): Event { return this._core.onCursorMove; } public get onData(): Event { return this._core.onData; } public get onLineFeed(): Event { return this._core.onLineFeed; } + public get onRender(): Event<{ start: number, end: number }> { return this._core.onRender; } public get onResize(): Event<{ cols: number, rows: number }> { return this._core.onResize; } public get onScroll(): Event { return this._core.onScroll; } public get onTitleChange(): Event { return this._core.onTitleChange; } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 0e6966be..26f970f1 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -696,6 +696,17 @@ declare module '@xterm/headless' { */ onLineFeed: IEvent; + /** + * Adds an event listener for when rows are _requested_ to be rendered. The + * event value contains the start row and end rows of the rendered area + * (ranges from `0` to `Terminal.rows - 1`). This differs from the regular + * xterm.js in that it doesn't actually do any rendering but requests + * rendering from the outside. This is useful for implementing a custom + * renderer on top of xterm-headless. + * @returns an `IDisposable` to stop listening. + */ + onRender: IEvent<{ start: number, end: number }>; + /** * Adds an event listener for when data has been parsed by the terminal, * after {@link write} is called. This event is useful to listen for any