add onImageAdded to api

This commit is contained in:
Anthony Kim
2026-03-03 12:19:14 -08:00
parent dedf3b1595
commit d6c872bb2c
5 changed files with 38 additions and 1 deletions
+5
View File
@@ -5,6 +5,7 @@
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
import { Emitter, type IEvent } from 'common/Event';
import { IIPHandler } from './IIPHandler';
import { ImageRenderer } from './ImageRenderer';
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
@@ -62,6 +63,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
private _disposables: IDisposable[] = [];
private _terminal: ITerminalExt | undefined;
private _handlers: Map<String, IResetHandler> = new Map();
private readonly _onImageAdded = new Emitter<void>();
public readonly onImageAdded: IEvent<void> = this._onImageAdded.event;
constructor(opts?: Partial<IImageAddonOptions>) {
this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);
@@ -74,6 +77,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
}
this._disposables.length = 0;
this._handlers.clear();
this._onImageAdded.dispose();
}
private _disposeLater(...args: IDisposable[]): void {
@@ -88,6 +92,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
// internal data structures
this._renderer = new ImageRenderer(terminal);
this._storage = new ImageStorage(terminal, this._renderer, this._opts);
this._storage.onImageAdded = () => this._onImageAdded.fire();
// enable size reports
if (this._opts.enableSizeReports) {
+2
View File
@@ -124,6 +124,7 @@ export class ImageStorage implements IDisposable {
private _pixelLimit: number = 2500000;
private _viewportMetrics: { cols: number, rows: number };
public onImageAdded: (() => void) | undefined;
public onImageDeleted: ((storageId: number) => void) | undefined;
constructor(
@@ -336,6 +337,7 @@ export class ImageStorage implements IDisposable {
// finally add the image
this._images.set(imageId, imgSpec);
this.onImageAdded?.();
return imageId;
}
@@ -196,6 +196,18 @@ test.describe('ImageAddon', () => {
});
test.describe('image lifecycle & eviction', () => {
test('onImageAdded fires for each image', async () => {
await ctx.page.evaluate(`
window._imageAddedCount = 0;
window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });
`);
await ctx.proxy.write(SIXEL_SEQ_0);
await pollFor(ctx.page, 'window._imageAddedCount', 1);
await ctx.proxy.write(SIXEL_SEQ_0);
await pollFor(ctx.page, 'window._imageAddedCount', 2);
await ctx.proxy.write(SIXEL_SEQ_0);
await pollFor(ctx.page, 'window._imageAddedCount', 3);
});
test('delete image once scrolled off', async () => {
await ctx.proxy.write(SIXEL_SEQ_0);
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);
@@ -2159,6 +2159,19 @@ test.describe('Kitty Graphics Protocol', () => {
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.has(${oldStorageId})`), false);
});
});
test.describe('onImageAdded callback', () => {
test('onImageAdded fires for each kitty image', async () => {
await ctx.page.evaluate(`
window._imageAddedCount = 0;
window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });
`);
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await pollFor(ctx.page, 'window._imageAddedCount', 1);
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\x1b\\`);
await pollFor(ctx.page, 'window._imageAddedCount', 2);
});
});
});
/**
+6 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { Terminal, ITerminalAddon } from '@xterm/xterm';
import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
declare module '@xterm/addon-image' {
export interface IImageAddonOptions {
@@ -116,6 +116,11 @@ declare module '@xterm/addon-image' {
*/
public showPlaceholder: boolean;
/**
* Event fired whenever a new image is added to storage.
*/
public readonly onImageAdded: IEvent<void>;
/**
* Get original image canvas at buffer position.
*/