/** * Copyright (c) 2024-2026 The xterm.js authors. All rights reserved. * @license MIT * * Minimal event utilities for xterm.js core. * Simplified from VS Code's event.ts - no leak detection/profiling. */ import { IDisposable, DisposableStore, toDisposable } from 'common/Lifecycle'; export interface IEvent { (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; } export class Emitter { private _listeners: { fn: (e: T) => any, thisArgs: any }[] = []; private _disposed = false; private _event: IEvent | undefined; public get event(): IEvent { if (this._event) { return this._event; } this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { if (this._disposed) { return toDisposable(() => {}); } const entry = { fn: listener, thisArgs }; this._listeners.push(entry); const result = toDisposable(() => { const idx = this._listeners.indexOf(entry); if (idx !== -1) { this._listeners.splice(idx, 1); } }); if (disposables) { if (Array.isArray(disposables)) { disposables.push(result); } else { disposables.add(result); } } return result; }; return this._event; } public fire(event: T): void { if (this._disposed) { return; } switch (this._listeners.length) { case 0: return; case 1: { const { fn, thisArgs } = this._listeners[0]; fn.call(thisArgs, event); return; } default: { // Snapshot listeners to allow modifications during iteration (2+ listeners) const listeners = this._listeners.slice(); for (const { fn, thisArgs } of listeners) { fn.call(thisArgs, event); } } } } public dispose(): void { if (this._disposed) { return; } this._disposed = true; this._listeners.length = 0; } } export namespace EventUtils { export function forward(from: IEvent, to: Emitter): IDisposable { return from(e => to.fire(e)); } export function map(event: IEvent, map: (i: I) => O): IEvent { return (listener: (e: O) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { return event(i => listener.call(thisArgs, map(i)), undefined, disposables); }; } export function any(...events: IEvent[]): IEvent; export function any(...events: IEvent[]): IEvent; export function any(...events: IEvent[]): IEvent { return (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { const store = new DisposableStore(); for (const event of events) { store.add(event(e => listener.call(thisArgs, e))); } if (disposables) { if (Array.isArray(disposables)) { disposables.push(store); } else { disposables.add(store); } } return store; }; } export function runAndSubscribe(event: IEvent, handler: (e: T) => void, initial: T): IDisposable; export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => void): IDisposable; export function runAndSubscribe(event: IEvent, handler: (e: T | undefined) => void, initial?: T): IDisposable { handler(initial); return event(e => handler(e)); } }