import { debugLog } from "./debug.ts"; import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; export type EditorFactory = NonNullable>; export interface EditorHost { getEditorComponent?(): EditorFactory | undefined; setEditorComponent?(factory: EditorFactory | undefined): void; } /** Owns composition and restoration without leaking stale session contexts. */ export class PiEditorAdapter { private installed?: EditorFactory; private previous?: EditorFactory; private previousFailed = false; private redraw?: () => void; install(ui: EditorHost, create: (...args: [...Parameters, ReturnType?]) => ReturnType): void { const current = ui.getEditorComponent?.(); if (current !== this.installed) { this.previous = current; this.previousFailed = false; } const previous = this.previous; this.installed = (tui, theme, keys) => { let base: ReturnType | undefined; try { base = previous?.(tui, theme, keys); } catch (error) { debugLog("Displaced editor factory failed; building a standalone editor", error); this.previousFailed = true; } const editor = create(tui, theme, keys, base); this.redraw = () => { editor.invalidate?.(); tui.requestRender(); }; return editor; }; for (const name of ["pi-zentui.editor-factory", "pi-zentui.editor-owner", "pi-zentui.editor-base-factory"]) { const symbol = Symbol.for(name); // SAFETY: foreign editor factories use these symbol properties as ownership metadata. const value = previous ? (previous as unknown as Record)[symbol] : undefined; if (value !== undefined) Object.defineProperty(this.installed, symbol, { value }); } ui.setEditorComponent?.(this.installed); } refresh(): void { try { this.redraw?.(); } catch (error) { debugLog("Draft editor redraw failed", error); } } dispose(ui: EditorHost): void { const installed = this.installed; // SAFETY: Zentui marks its factory with this shared ownership symbol. const isZentui = Boolean(this.previous && (this.previous as unknown as Record)[Symbol.for("pi-zentui.editor-factory")]); const previous = isZentui || this.previousFailed ? undefined : this.previous; const restore = (): void => { try { ui.setEditorComponent?.(previous); } catch (error) { debugLog("Restoring the displaced editor failed; clearing it", error); try { ui.setEditorComponent?.(undefined); } catch { /* disposed UI */ } } }; if (installed && ui.getEditorComponent?.() === installed) restore(); else if (installed) { const timer = setTimeout(() => { try { if (ui.getEditorComponent?.() === installed) restore(); } catch { /* disposed UI */ } }, 0); timer.unref?.(); } this.installed = this.previous = undefined; this.previousFailed = false; this.redraw = undefined; } }