restty 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8576,6 +8576,10 @@ class OutputFilter {
8576
8576
  clipboardWrite;
8577
8577
  clipboardRead;
8578
8578
  getDefaultColors;
8579
+ semanticPromptSeen = false;
8580
+ promptClickEvents = false;
8581
+ promptInputActive = false;
8582
+ commandRunning = false;
8579
8583
  constructor(options) {
8580
8584
  this.getCursorPosition = options.getCursorPosition;
8581
8585
  this.sendReply = options.sendReply;
@@ -8607,6 +8611,80 @@ class OutputFilter {
8607
8611
  isSynchronizedOutput() {
8608
8612
  return this.synchronizedOutput;
8609
8613
  }
8614
+ isPromptClickEventsEnabled() {
8615
+ return this.semanticPromptSeen && this.promptClickEvents && this.promptInputActive && !this.commandRunning && !this.altScreen;
8616
+ }
8617
+ encodePromptClickEvent(cell) {
8618
+ if (!this.isPromptClickEventsEnabled())
8619
+ return "";
8620
+ const row = Math.max(1, Math.floor(cell.row) + 1);
8621
+ const col = Math.max(1, Math.floor(cell.col) + 1);
8622
+ return `\x1B[<0;${col};${row}M`;
8623
+ }
8624
+ readOsc133BoolOption(options, key) {
8625
+ if (!options)
8626
+ return null;
8627
+ const prefix = `${key}=`;
8628
+ const fields = options.split(";");
8629
+ for (let i = 0;i < fields.length; i += 1) {
8630
+ const field = fields[i];
8631
+ if (!field.startsWith(prefix))
8632
+ continue;
8633
+ const value = field.slice(prefix.length);
8634
+ if (value === "1")
8635
+ return true;
8636
+ if (value === "0")
8637
+ return false;
8638
+ return null;
8639
+ }
8640
+ return null;
8641
+ }
8642
+ observeSemanticPromptOsc(action, options) {
8643
+ const clickEvents = this.readOsc133BoolOption(options, "click_events");
8644
+ if (clickEvents !== null)
8645
+ this.promptClickEvents = clickEvents;
8646
+ switch (action) {
8647
+ case "A":
8648
+ case "B":
8649
+ case "I":
8650
+ this.semanticPromptSeen = true;
8651
+ this.promptInputActive = true;
8652
+ this.commandRunning = false;
8653
+ break;
8654
+ case "C":
8655
+ this.semanticPromptSeen = true;
8656
+ this.promptInputActive = false;
8657
+ this.commandRunning = true;
8658
+ break;
8659
+ case "D":
8660
+ this.semanticPromptSeen = true;
8661
+ this.promptInputActive = false;
8662
+ this.commandRunning = false;
8663
+ break;
8664
+ case "P":
8665
+ this.semanticPromptSeen = true;
8666
+ break;
8667
+ default:
8668
+ break;
8669
+ }
8670
+ }
8671
+ observeOsc(seq) {
8672
+ const content = seq.slice(2);
8673
+ const sep = content.indexOf(";");
8674
+ if (sep < 0)
8675
+ return;
8676
+ const code = content.slice(0, sep);
8677
+ if (code !== "133")
8678
+ return;
8679
+ const rest = content.slice(sep + 1);
8680
+ if (!rest)
8681
+ return;
8682
+ const action = rest[0] ?? "";
8683
+ if (!action)
8684
+ return;
8685
+ const options = rest.length > 2 && rest[1] === ";" ? rest.slice(2) : "";
8686
+ this.observeSemanticPromptOsc(action, options);
8687
+ }
8610
8688
  replyOscColor(code, rgb) {
8611
8689
  const toHex4 = (value) => Math.round(Math.max(0, Math.min(255, value)) * 257).toString(16).padStart(4, "0");
8612
8690
  const r = toHex4(rgb[0]);
@@ -8749,6 +8827,7 @@ class OutputFilter {
8749
8827
  break;
8750
8828
  }
8751
8829
  const seq2 = data.slice(i, j2);
8830
+ this.observeOsc(seq2);
8752
8831
  if (!this.handleOsc(seq2)) {
8753
8832
  result += data.slice(i, j2 + terminatorLen);
8754
8833
  }
@@ -8858,6 +8937,8 @@ function createInputHandler(options = {}) {
8858
8937
  isFocusReporting: () => filter.isFocusReporting(),
8859
8938
  isAltScreen: () => filter.isAltScreen(),
8860
8939
  isSynchronizedOutput: () => filter.isSynchronizedOutput(),
8940
+ isPromptClickEventsEnabled: () => filter.isPromptClickEventsEnabled(),
8941
+ encodePromptClickEvent: (cell) => filter.encodePromptClickEvent(cell),
8861
8942
  sendMouseEvent: (kind, event) => mouse.sendMouseEvent(kind, event)
8862
8943
  };
8863
8944
  }
@@ -26723,6 +26804,17 @@ function buildFontAtlasIfNeeded(params) {
26723
26804
  };
26724
26805
  }
26725
26806
 
26807
+ // src/app/clipboard-paste.ts
26808
+ function readPastePayloadFromDataTransfer(dataTransfer) {
26809
+ if (!dataTransfer)
26810
+ return null;
26811
+ const text = dataTransfer.getData("text/plain") || "";
26812
+ return text ? {
26813
+ kind: "text",
26814
+ text
26815
+ } : null;
26816
+ }
26817
+
26726
26818
  // src/app/font-sources.ts
26727
26819
  var DEFAULT_FONT_SOURCES = [
26728
26820
  {
@@ -50315,6 +50407,8 @@ var DEFAULT_EMOJI_CONSTRAINT = {
50315
50407
  };
50316
50408
  function createResttyApp(options) {
50317
50409
  const { canvas: canvasInput, imeInput: imeInputInput, elements, callbacks } = options;
50410
+ const beforeInputHook = options.beforeInput;
50411
+ const beforeRenderOutputHook = options.beforeRenderOutput;
50318
50412
  const session = options.session ?? getDefaultResttyAppSession();
50319
50413
  const textShaper = exports_dist;
50320
50414
  if (!canvasInput) {
@@ -50558,6 +50652,11 @@ function createResttyApp(options) {
50558
50652
  panLastY: 0,
50559
50653
  pendingTimer: 0
50560
50654
  };
50655
+ const desktopSelectionState = {
50656
+ pendingPointerId: null,
50657
+ pendingCell: null,
50658
+ startedWithActiveSelection: false
50659
+ };
50561
50660
  const linkState = {
50562
50661
  hoverId: 0,
50563
50662
  hoverUri: ""
@@ -50577,11 +50676,7 @@ function createResttyApp(options) {
50577
50676
  function updateCanvasCursor() {
50578
50677
  if (!canvas)
50579
50678
  return;
50580
- if (selectionState.dragging || selectionState.active) {
50581
- canvas.style.cursor = "text";
50582
- return;
50583
- }
50584
- canvas.style.cursor = linkState.hoverId ? "pointer" : "default";
50679
+ canvas.style.cursor = "text";
50585
50680
  }
50586
50681
  function isTouchPointer(event) {
50587
50682
  return event.pointerType === "touch";
@@ -50595,6 +50690,11 @@ function createResttyApp(options) {
50595
50690
  touchSelectionState.pendingCell = null;
50596
50691
  touchSelectionState.pendingStartedAt = 0;
50597
50692
  }
50693
+ function clearPendingDesktopSelection() {
50694
+ desktopSelectionState.pendingPointerId = null;
50695
+ desktopSelectionState.pendingCell = null;
50696
+ desktopSelectionState.startedWithActiveSelection = false;
50697
+ }
50598
50698
  function tryActivatePendingTouchSelection(pointerId) {
50599
50699
  if (touchSelectionMode !== "long-press")
50600
50700
  return false;
@@ -50610,6 +50710,7 @@ function createResttyApp(options) {
50610
50710
  return true;
50611
50711
  }
50612
50712
  function beginSelectionDrag(cell, pointerId) {
50713
+ clearPendingDesktopSelection();
50613
50714
  selectionState.active = true;
50614
50715
  selectionState.dragging = true;
50615
50716
  selectionState.anchor = cell;
@@ -51411,6 +51512,7 @@ function createResttyApp(options) {
51411
51512
  });
51412
51513
  inputHandler.setMouseMode("auto");
51413
51514
  function clearSelection() {
51515
+ clearPendingDesktopSelection();
51414
51516
  selectionState.active = false;
51415
51517
  selectionState.dragging = false;
51416
51518
  selectionState.anchor = null;
@@ -51593,15 +51695,18 @@ function createResttyApp(options) {
51593
51695
  function sendKeyInput(text, source = "key") {
51594
51696
  if (!text)
51595
51697
  return;
51698
+ const intercepted = runBeforeInputHook(text, source);
51699
+ if (!intercepted)
51700
+ return;
51596
51701
  if (source !== "program" && (selectionState.active || selectionState.dragging)) {
51597
51702
  clearSelection();
51598
51703
  }
51599
51704
  if (ptyTransport.isConnected()) {
51600
- const payload = inputHandler.mapKeyForPty(text);
51705
+ const payload = inputHandler.mapKeyForPty(intercepted);
51601
51706
  ptyTransport.sendInput(payload);
51602
51707
  return;
51603
51708
  }
51604
- sendInput(text, source);
51709
+ sendInput(intercepted, source, { skipHooks: true });
51605
51710
  }
51606
51711
  function formatPasteText(text) {
51607
51712
  if (!inputHandler?.isBracketedPaste?.())
@@ -51613,6 +51718,13 @@ function createResttyApp(options) {
51613
51718
  return;
51614
51719
  sendKeyInput(formatPasteText(text));
51615
51720
  }
51721
+ function sendPastePayloadFromDataTransfer(dataTransfer) {
51722
+ const payload = readPastePayloadFromDataTransfer(dataTransfer);
51723
+ if (!payload)
51724
+ return false;
51725
+ sendPasteText(payload.text);
51726
+ return true;
51727
+ }
51616
51728
  function openLink(uri) {
51617
51729
  if (!uri || typeof window === "undefined")
51618
51730
  return;
@@ -51660,6 +51772,7 @@ function createResttyApp(options) {
51660
51772
  }
51661
51773
  }
51662
51774
  if (shouldRoutePointerToAppMouse(event.shiftKey) && !shouldPreferLocalPrimarySelection(event) && inputHandler.sendMouseEvent("down", event)) {
51775
+ clearPendingDesktopSelection();
51663
51776
  event.preventDefault();
51664
51777
  canvas.setPointerCapture?.(event.pointerId);
51665
51778
  return;
@@ -51695,7 +51808,9 @@ function createResttyApp(options) {
51695
51808
  event.preventDefault();
51696
51809
  const cell = normalizeSelectionCell(positionToCell(event));
51697
51810
  updateLinkHover(cell);
51698
- beginSelectionDrag(cell, event.pointerId);
51811
+ desktopSelectionState.pendingPointerId = event.pointerId;
51812
+ desktopSelectionState.pendingCell = cell;
51813
+ desktopSelectionState.startedWithActiveSelection = selectionState.active;
51699
51814
  };
51700
51815
  const onPointerMove = (event) => {
51701
51816
  if (scrollbarDragState.pointerId === event.pointerId) {
@@ -51747,6 +51862,19 @@ function createResttyApp(options) {
51747
51862
  return;
51748
51863
  }
51749
51864
  const cell = normalizeSelectionCell(positionToCell(event));
51865
+ if (desktopSelectionState.pendingPointerId === event.pointerId && desktopSelectionState.pendingCell) {
51866
+ const anchor = desktopSelectionState.pendingCell;
51867
+ if (anchor.row !== cell.row || anchor.col !== cell.col) {
51868
+ beginSelectionDrag(anchor, event.pointerId);
51869
+ selectionState.focus = cell;
51870
+ updateLinkHover(null);
51871
+ updateCanvasCursor();
51872
+ needsRender = true;
51873
+ return;
51874
+ }
51875
+ updateLinkHover(cell);
51876
+ return;
51877
+ }
51750
51878
  if (selectionState.dragging) {
51751
51879
  event.preventDefault();
51752
51880
  selectionState.focus = cell;
@@ -51794,6 +51922,12 @@ function createResttyApp(options) {
51794
51922
  return;
51795
51923
  }
51796
51924
  const cell = normalizeSelectionCell(positionToCell(event));
51925
+ const clearSelectionFromClick = desktopSelectionState.pendingPointerId === event.pointerId && desktopSelectionState.startedWithActiveSelection && !selectionState.dragging;
51926
+ if (desktopSelectionState.pendingPointerId === event.pointerId) {
51927
+ clearPendingDesktopSelection();
51928
+ }
51929
+ if (clearSelectionFromClick)
51930
+ clearSelection();
51797
51931
  if (selectionState.dragging) {
51798
51932
  event.preventDefault();
51799
51933
  selectionState.dragging = false;
@@ -51811,6 +51945,14 @@ function createResttyApp(options) {
51811
51945
  }
51812
51946
  updateLinkHover(cell);
51813
51947
  }
51948
+ if (!selectionState.active && event.button === 0 && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && inputHandler.isPromptClickEventsEnabled()) {
51949
+ const seq = inputHandler.encodePromptClickEvent(cell);
51950
+ if (seq) {
51951
+ event.preventDefault();
51952
+ sendKeyInput(seq);
51953
+ return;
51954
+ }
51955
+ }
51814
51956
  if (!selectionState.active && event.button === 0 && linkState.hoverUri) {
51815
51957
  openLink(linkState.hoverUri);
51816
51958
  }
@@ -51819,6 +51961,9 @@ function createResttyApp(options) {
51819
51961
  if (scrollbarDragState.pointerId === event.pointerId) {
51820
51962
  scrollbarDragState.pointerId = null;
51821
51963
  }
51964
+ if (desktopSelectionState.pendingPointerId === event.pointerId) {
51965
+ clearPendingDesktopSelection();
51966
+ }
51822
51967
  if (isTouchPointer(event)) {
51823
51968
  if (touchSelectionState.pendingPointerId === event.pointerId) {
51824
51969
  clearPendingTouchSelection();
@@ -51911,13 +52056,16 @@ function createResttyApp(options) {
51911
52056
  if (imeState.composing)
51912
52057
  return;
51913
52058
  if (event.inputType === "insertFromPaste") {
52059
+ event.preventDefault();
52060
+ suppressNextInput = true;
51914
52061
  const pasteText = event.dataTransfer?.getData("text/plain") || event.data || "";
51915
52062
  if (pasteText) {
51916
- event.preventDefault();
51917
- suppressNextInput = true;
51918
52063
  sendPasteText(pasteText);
51919
52064
  imeInput.value = "";
52065
+ return;
51920
52066
  }
52067
+ sendPastePayloadFromDataTransfer(event.dataTransfer);
52068
+ imeInput.value = "";
51921
52069
  return;
51922
52070
  }
51923
52071
  const text = inputHandler.encodeBeforeInput(event);
@@ -51954,13 +52102,16 @@ function createResttyApp(options) {
51954
52102
  const onPaste = (event) => {
51955
52103
  if (!wasmReady || !wasmHandle)
51956
52104
  return;
52105
+ event.preventDefault();
52106
+ suppressNextInput = true;
51957
52107
  const text = event.clipboardData?.getData("text/plain") || "";
51958
52108
  if (text) {
51959
- event.preventDefault();
51960
- suppressNextInput = true;
51961
52109
  sendPasteText(text);
51962
52110
  imeInput.value = "";
52111
+ return;
51963
52112
  }
52113
+ sendPastePayloadFromDataTransfer(event.clipboardData);
52114
+ imeInput.value = "";
51964
52115
  };
51965
52116
  imeInput.addEventListener("compositionstart", onCompositionStart);
51966
52117
  imeInput.addEventListener("compositionupdate", onCompositionUpdate);
@@ -53235,6 +53386,36 @@ function createResttyApp(options) {
53235
53386
  needsRender = true;
53236
53387
  lastRenderTime = 0;
53237
53388
  }
53389
+ function resize(cols, rows) {
53390
+ const nextCols = Math.max(1, Math.floor(Number(cols)));
53391
+ const nextRows = Math.max(1, Math.floor(Number(rows)));
53392
+ if (!Number.isFinite(nextCols) || !Number.isFinite(nextRows))
53393
+ return;
53394
+ const dpr = window.devicePixelRatio || 1;
53395
+ if (dpr !== currentDpr) {
53396
+ currentDpr = dpr;
53397
+ if (dprEl)
53398
+ dprEl.textContent = dpr.toFixed(2);
53399
+ callbacks?.onDpr?.(dpr);
53400
+ }
53401
+ const metrics = computeCellMetrics2();
53402
+ if (!metrics)
53403
+ return;
53404
+ canvas.width = Math.max(1, nextCols * metrics.cellW);
53405
+ canvas.height = Math.max(1, nextRows * metrics.cellH);
53406
+ if (sizeEl)
53407
+ sizeEl.textContent = `${canvas.width}x${canvas.height}`;
53408
+ callbacks?.onCanvasSize?.(canvas.width, canvas.height);
53409
+ resizeState.dpr = currentDpr;
53410
+ resizeState.active = true;
53411
+ resizeState.lastAt = performance.now();
53412
+ resizeState.cols = nextCols;
53413
+ resizeState.rows = nextRows;
53414
+ updateGrid();
53415
+ scheduleTerminalResizeCommit(nextCols, nextRows, { immediate: true });
53416
+ needsRender = true;
53417
+ lastRenderTime = 0;
53418
+ }
53238
53419
  function scheduleSizeUpdate() {
53239
53420
  updateSize();
53240
53421
  if (sizeRaf)
@@ -53244,22 +53425,35 @@ function createResttyApp(options) {
53244
53425
  updateSize();
53245
53426
  });
53246
53427
  }
53428
+ function focusTypingInput() {
53429
+ canvas.focus({ preventScroll: true });
53430
+ if (!imeInput)
53431
+ return;
53432
+ imeInput.focus({ preventScroll: true });
53433
+ if (typeof document !== "undefined" && document.activeElement !== imeInput) {
53434
+ requestAnimationFrame(() => {
53435
+ if (document.activeElement === canvas)
53436
+ imeInput.focus({ preventScroll: true });
53437
+ });
53438
+ }
53439
+ }
53440
+ function focus() {
53441
+ focusTypingInput();
53442
+ isFocused = typeof document !== "undefined" && imeInput ? document.activeElement === canvas || document.activeElement === imeInput : true;
53443
+ }
53444
+ function blur() {
53445
+ if (imeInput && document.activeElement === imeInput) {
53446
+ imeInput.blur();
53447
+ }
53448
+ if (document.activeElement === canvas) {
53449
+ canvas.blur();
53450
+ }
53451
+ isFocused = false;
53452
+ }
53247
53453
  function bindFocusEvents() {
53248
53454
  if (!attachCanvasEvents)
53249
53455
  return;
53250
53456
  canvas.tabIndex = 0;
53251
- const focusTypingInput = () => {
53252
- canvas.focus({ preventScroll: true });
53253
- if (!imeInput)
53254
- return;
53255
- imeInput.focus({ preventScroll: true });
53256
- if (typeof document !== "undefined" && document.activeElement !== imeInput) {
53257
- requestAnimationFrame(() => {
53258
- if (document.activeElement === canvas)
53259
- imeInput.focus({ preventScroll: true });
53260
- });
53261
- }
53262
- };
53263
53457
  const handleFocus = () => {
53264
53458
  isFocused = true;
53265
53459
  focusTypingInput();
@@ -56063,12 +56257,48 @@ function createResttyApp(options) {
56063
56257
  return text.replace(/\r?\n/g, `\r
56064
56258
  `);
56065
56259
  }
56066
- function sendInput(text, source = "program") {
56260
+ function runBeforeInputHook(text, source) {
56261
+ if (!beforeInputHook)
56262
+ return text;
56263
+ try {
56264
+ const next = beforeInputHook({ text, source });
56265
+ if (next === null)
56266
+ return null;
56267
+ if (typeof next === "string")
56268
+ return next;
56269
+ return text;
56270
+ } catch (error) {
56271
+ console.error("[restty] beforeInput hook error:", error);
56272
+ return text;
56273
+ }
56274
+ }
56275
+ function runBeforeRenderOutputHook(text, source) {
56276
+ if (!beforeRenderOutputHook)
56277
+ return text;
56278
+ try {
56279
+ const next = beforeRenderOutputHook({ text, source });
56280
+ if (next === null)
56281
+ return null;
56282
+ if (typeof next === "string")
56283
+ return next;
56284
+ return text;
56285
+ } catch (error) {
56286
+ console.error("[restty] beforeRenderOutput hook error:", error);
56287
+ return text;
56288
+ }
56289
+ }
56290
+ function sendInput(text, source = "program", options2 = {}) {
56067
56291
  if (!wasmReady || !wasm || !wasmHandle)
56068
56292
  return;
56069
56293
  if (!text)
56070
56294
  return;
56071
- const normalized = source === "pty" ? text : normalizeNewlines(text);
56295
+ let intercepted = text;
56296
+ if (!options2.skipHooks) {
56297
+ intercepted = source === "pty" ? runBeforeRenderOutputHook(text, source) : runBeforeInputHook(text, source);
56298
+ }
56299
+ if (!intercepted)
56300
+ return;
56301
+ const normalized = source === "pty" ? intercepted : normalizeNewlines(intercepted);
56072
56302
  if (source === "key") {
56073
56303
  const bytes = textEncoder3.encode(normalized);
56074
56304
  const hex = Array.from(bytes, (b3) => b3.toString(16).padStart(2, "0")).join(" ");
@@ -56134,6 +56364,8 @@ function createResttyApp(options) {
56134
56364
  return false;
56135
56365
  }
56136
56366
  async function pasteFromClipboard() {
56367
+ if (typeof navigator === "undefined" || !navigator.clipboard)
56368
+ return false;
56137
56369
  try {
56138
56370
  const text = await navigator.clipboard.readText();
56139
56371
  if (text) {
@@ -56145,6 +56377,15 @@ function createResttyApp(options) {
56145
56377
  }
56146
56378
  return false;
56147
56379
  }
56380
+ async function handlePasteShortcut(event) {
56381
+ const pasted = await pasteFromClipboard();
56382
+ if (pasted)
56383
+ return;
56384
+ const seq = inputHandler.encodeKeyEvent(event);
56385
+ if (!seq)
56386
+ return;
56387
+ sendKeyInput(seq);
56388
+ }
56148
56389
  function clearScreen() {
56149
56390
  sendInput("\x1B[2J\x1B[H");
56150
56391
  }
@@ -56193,7 +56434,9 @@ function createResttyApp(options) {
56193
56434
  }
56194
56435
  if (wantsPaste) {
56195
56436
  event.preventDefault();
56196
- pasteFromClipboard();
56437
+ if (imeInput)
56438
+ imeInput.focus({ preventScroll: true });
56439
+ handlePasteShortcut(event);
56197
56440
  return;
56198
56441
  }
56199
56442
  const seq = inputHandler.encodeKeyEvent(event);
@@ -56395,6 +56638,9 @@ function createResttyApp(options) {
56395
56638
  copySelectionToClipboard,
56396
56639
  pasteFromClipboard,
56397
56640
  dumpAtlasForCodepoint,
56641
+ resize,
56642
+ focus,
56643
+ blur,
56398
56644
  updateSize,
56399
56645
  getBackend: () => backend
56400
56646
  };
@@ -56514,6 +56760,8 @@ function createResttyAppPaneManager(options) {
56514
56760
  }
56515
56761
 
56516
56762
  // src/app/restty.ts
56763
+ var RESTTY_PLUGIN_API_VERSION = 1;
56764
+
56517
56765
  class ResttyPaneHandle {
56518
56766
  resolvePane;
56519
56767
  constructor(resolvePane) {
@@ -56573,6 +56821,15 @@ class ResttyPaneHandle {
56573
56821
  dumpAtlasForCodepoint(cp) {
56574
56822
  this.resolvePane().app.dumpAtlasForCodepoint(cp);
56575
56823
  }
56824
+ resize(cols, rows) {
56825
+ this.resolvePane().app.resize(cols, rows);
56826
+ }
56827
+ focus() {
56828
+ this.resolvePane().app.focus();
56829
+ }
56830
+ blur() {
56831
+ this.resolvePane().app.blur();
56832
+ }
56576
56833
  updateSize(force) {
56577
56834
  this.resolvePane().app.updateSize(force);
56578
56835
  }
@@ -56587,25 +56844,105 @@ class ResttyPaneHandle {
56587
56844
  class Restty {
56588
56845
  paneManager;
56589
56846
  fontSources;
56847
+ pluginListeners = new Map;
56848
+ pluginRuntimes = new Map;
56849
+ pluginDiagnostics = new Map;
56850
+ inputInterceptors = [];
56851
+ outputInterceptors = [];
56852
+ lifecycleHooks = [];
56853
+ renderHooks = [];
56854
+ nextInterceptorId = 1;
56855
+ nextInterceptorOrder = 1;
56590
56856
  constructor(options) {
56591
- const { createInitialPane = true, appOptions, fontSources, ...paneManagerOptions } = options;
56857
+ const {
56858
+ createInitialPane = true,
56859
+ appOptions,
56860
+ fontSources,
56861
+ onPaneCreated,
56862
+ onPaneClosed,
56863
+ onPaneSplit,
56864
+ onActivePaneChange,
56865
+ onLayoutChanged,
56866
+ ...paneManagerOptions
56867
+ } = options;
56592
56868
  this.fontSources = fontSources ? [...fontSources] : undefined;
56593
56869
  const mergedAppOptions = (context) => {
56594
56870
  const resolved = typeof appOptions === "function" ? appOptions(context) : appOptions ?? {};
56595
- if (!this.fontSources)
56596
- return resolved;
56871
+ const resolvedBeforeInput = resolved.beforeInput;
56872
+ const resolvedBeforeRenderOutput = resolved.beforeRenderOutput;
56597
56873
  return {
56598
56874
  ...resolved,
56599
- fontSources: this.fontSources
56875
+ ...this.fontSources ? { fontSources: this.fontSources } : {},
56876
+ beforeInput: ({ text, source }) => {
56877
+ const maybeUserText = resolvedBeforeInput?.({ text, source });
56878
+ if (maybeUserText === null)
56879
+ return null;
56880
+ const current = maybeUserText === undefined ? text : maybeUserText;
56881
+ return this.applyInputInterceptors(context.id, current, source);
56882
+ },
56883
+ beforeRenderOutput: ({ text, source }) => {
56884
+ this.runRenderHooks({
56885
+ phase: "before",
56886
+ paneId: context.id,
56887
+ text,
56888
+ source,
56889
+ dropped: false
56890
+ });
56891
+ const maybeUserText = resolvedBeforeRenderOutput?.({ text, source });
56892
+ if (maybeUserText === null) {
56893
+ this.runRenderHooks({
56894
+ phase: "after",
56895
+ paneId: context.id,
56896
+ text,
56897
+ source,
56898
+ dropped: true
56899
+ });
56900
+ return null;
56901
+ }
56902
+ const current = maybeUserText === undefined ? text : maybeUserText;
56903
+ const next = this.applyOutputInterceptors(context.id, current, source);
56904
+ this.runRenderHooks({
56905
+ phase: "after",
56906
+ paneId: context.id,
56907
+ text: next === null ? current : next,
56908
+ source,
56909
+ dropped: next === null
56910
+ });
56911
+ return next;
56912
+ }
56600
56913
  };
56601
56914
  };
56602
56915
  this.paneManager = createResttyAppPaneManager({
56603
56916
  ...paneManagerOptions,
56604
- appOptions: mergedAppOptions
56917
+ appOptions: mergedAppOptions,
56918
+ onPaneCreated: (pane) => {
56919
+ this.emitPluginEvent("pane:created", { paneId: pane.id });
56920
+ onPaneCreated?.(pane);
56921
+ },
56922
+ onPaneClosed: (pane) => {
56923
+ this.emitPluginEvent("pane:closed", { paneId: pane.id });
56924
+ onPaneClosed?.(pane);
56925
+ },
56926
+ onPaneSplit: (sourcePane, createdPane, direction) => {
56927
+ this.emitPluginEvent("pane:split", {
56928
+ sourcePaneId: sourcePane.id,
56929
+ createdPaneId: createdPane.id,
56930
+ direction
56931
+ });
56932
+ onPaneSplit?.(sourcePane, createdPane, direction);
56933
+ },
56934
+ onActivePaneChange: (pane) => {
56935
+ this.emitPluginEvent("pane:active-changed", { paneId: pane?.id ?? null });
56936
+ onActivePaneChange?.(pane);
56937
+ },
56938
+ onLayoutChanged: () => {
56939
+ this.emitPluginEvent("layout:changed", {});
56940
+ onLayoutChanged?.();
56941
+ }
56605
56942
  });
56606
56943
  if (createInitialPane) {
56607
56944
  const focus = typeof createInitialPane === "object" ? createInitialPane.focus ?? true : true;
56608
- this.paneManager.createInitialPane({ focus });
56945
+ this.createInitialPane({ focus });
56609
56946
  }
56610
56947
  }
56611
56948
  getPanes() {
@@ -56656,16 +56993,63 @@ class Restty {
56656
56993
  await Promise.all(updates);
56657
56994
  }
56658
56995
  createInitialPane(options) {
56659
- return this.paneManager.createInitialPane(options);
56996
+ this.runLifecycleHooks({ phase: "before", action: "create-initial-pane" });
56997
+ const pane = this.paneManager.createInitialPane(options);
56998
+ this.runLifecycleHooks({
56999
+ phase: "after",
57000
+ action: "create-initial-pane",
57001
+ paneId: pane.id,
57002
+ ok: true
57003
+ });
57004
+ return pane;
56660
57005
  }
56661
57006
  splitActivePane(direction) {
56662
- return this.paneManager.splitActivePane(direction);
57007
+ const sourcePaneId = this.getActivePane()?.id ?? null;
57008
+ this.runLifecycleHooks({
57009
+ phase: "before",
57010
+ action: "split-active-pane",
57011
+ paneId: sourcePaneId,
57012
+ direction
57013
+ });
57014
+ const pane = this.paneManager.splitActivePane(direction);
57015
+ this.runLifecycleHooks({
57016
+ phase: "after",
57017
+ action: "split-active-pane",
57018
+ sourcePaneId: sourcePaneId ?? undefined,
57019
+ createdPaneId: pane?.id ?? null,
57020
+ direction,
57021
+ ok: !!pane
57022
+ });
57023
+ return pane;
56663
57024
  }
56664
57025
  splitPane(id, direction) {
56665
- return this.paneManager.splitPane(id, direction);
57026
+ this.runLifecycleHooks({
57027
+ phase: "before",
57028
+ action: "split-pane",
57029
+ paneId: id,
57030
+ direction
57031
+ });
57032
+ const pane = this.paneManager.splitPane(id, direction);
57033
+ this.runLifecycleHooks({
57034
+ phase: "after",
57035
+ action: "split-pane",
57036
+ sourcePaneId: id,
57037
+ createdPaneId: pane?.id ?? null,
57038
+ direction,
57039
+ ok: !!pane
57040
+ });
57041
+ return pane;
56666
57042
  }
56667
57043
  closePane(id) {
56668
- return this.paneManager.closePane(id);
57044
+ this.runLifecycleHooks({ phase: "before", action: "close-pane", paneId: id });
57045
+ const ok = this.paneManager.closePane(id);
57046
+ this.runLifecycleHooks({
57047
+ phase: "after",
57048
+ action: "close-pane",
57049
+ paneId: id,
57050
+ ok
57051
+ });
57052
+ return ok;
56669
57053
  }
56670
57054
  getPaneStyleOptions() {
56671
57055
  return this.paneManager.getStyleOptions();
@@ -56674,10 +57058,34 @@ class Restty {
56674
57058
  this.paneManager.setStyleOptions(options);
56675
57059
  }
56676
57060
  setActivePane(id, options) {
57061
+ this.runLifecycleHooks({
57062
+ phase: "before",
57063
+ action: "set-active-pane",
57064
+ paneId: id
57065
+ });
56677
57066
  this.paneManager.setActivePane(id, options);
57067
+ const activePaneId = this.getActivePane()?.id ?? null;
57068
+ this.runLifecycleHooks({
57069
+ phase: "after",
57070
+ action: "set-active-pane",
57071
+ paneId: activePaneId,
57072
+ ok: activePaneId === id
57073
+ });
56678
57074
  }
56679
57075
  markPaneFocused(id, options) {
57076
+ this.runLifecycleHooks({
57077
+ phase: "before",
57078
+ action: "mark-pane-focused",
57079
+ paneId: id
57080
+ });
56680
57081
  this.paneManager.markPaneFocused(id, options);
57082
+ const focusedPaneId = this.getFocusedPane()?.id ?? null;
57083
+ this.runLifecycleHooks({
57084
+ phase: "after",
57085
+ action: "mark-pane-focused",
57086
+ paneId: focusedPaneId,
57087
+ ok: focusedPaneId === id
57088
+ });
56681
57089
  }
56682
57090
  requestLayoutSync() {
56683
57091
  this.paneManager.requestLayoutSync();
@@ -56685,14 +57093,193 @@ class Restty {
56685
57093
  hideContextMenu() {
56686
57094
  this.paneManager.hideContextMenu();
56687
57095
  }
57096
+ async use(plugin, options) {
57097
+ if (!plugin || typeof plugin !== "object") {
57098
+ throw new Error("Restty plugin must be an object");
57099
+ }
57100
+ const pluginId = plugin.id?.trim?.() ?? "";
57101
+ if (!pluginId) {
57102
+ throw new Error("Restty plugin id is required");
57103
+ }
57104
+ if (typeof plugin.activate !== "function") {
57105
+ throw new Error(`Restty plugin ${pluginId} must define activate(context)`);
57106
+ }
57107
+ if (this.pluginRuntimes.has(pluginId))
57108
+ return;
57109
+ try {
57110
+ this.assertPluginCompatibility(pluginId, plugin);
57111
+ } catch (error) {
57112
+ this.pluginDiagnostics.set(pluginId, {
57113
+ id: pluginId,
57114
+ version: plugin.version?.trim?.() || null,
57115
+ apiVersion: Number.isFinite(plugin.apiVersion) ? Number(plugin.apiVersion) : null,
57116
+ requires: plugin.requires ?? null,
57117
+ active: false,
57118
+ activatedAt: null,
57119
+ lastError: this.errorToMessage(error)
57120
+ });
57121
+ throw error;
57122
+ }
57123
+ const runtime2 = {
57124
+ plugin: this.normalizePluginMetadata(plugin, pluginId),
57125
+ cleanup: null,
57126
+ activatedAt: Date.now(),
57127
+ options,
57128
+ disposers: []
57129
+ };
57130
+ this.pluginDiagnostics.set(pluginId, {
57131
+ id: pluginId,
57132
+ version: runtime2.plugin.version?.trim?.() || null,
57133
+ apiVersion: Number.isFinite(runtime2.plugin.apiVersion) ? Number(runtime2.plugin.apiVersion) : null,
57134
+ requires: runtime2.plugin.requires ?? null,
57135
+ active: false,
57136
+ activatedAt: null,
57137
+ lastError: null
57138
+ });
57139
+ this.pluginRuntimes.set(pluginId, runtime2);
57140
+ try {
57141
+ const cleanup = await runtime2.plugin.activate(this.createPluginContext(runtime2), runtime2.options);
57142
+ runtime2.cleanup = this.normalizePluginCleanup(cleanup);
57143
+ runtime2.activatedAt = Date.now();
57144
+ this.updatePluginDiagnostic(pluginId, {
57145
+ active: true,
57146
+ activatedAt: runtime2.activatedAt,
57147
+ lastError: null
57148
+ });
57149
+ this.emitPluginEvent("plugin:activated", { pluginId });
57150
+ } catch (error) {
57151
+ this.teardownPluginRuntime(runtime2);
57152
+ this.pluginRuntimes.delete(pluginId);
57153
+ this.updatePluginDiagnostic(pluginId, {
57154
+ active: false,
57155
+ activatedAt: null,
57156
+ lastError: this.errorToMessage(error)
57157
+ });
57158
+ throw error;
57159
+ }
57160
+ }
57161
+ async loadPlugins(manifest, registry) {
57162
+ const results = [];
57163
+ for (let i3 = 0;i3 < manifest.length; i3 += 1) {
57164
+ const item = manifest[i3];
57165
+ const pluginId = item.id?.trim?.() ?? "";
57166
+ if (!pluginId) {
57167
+ results.push({
57168
+ id: "",
57169
+ status: "failed",
57170
+ error: "Restty plugin manifest entry is missing id"
57171
+ });
57172
+ continue;
57173
+ }
57174
+ if (item.enabled === false) {
57175
+ results.push({ id: pluginId, status: "skipped", error: null });
57176
+ continue;
57177
+ }
57178
+ const entry = this.lookupPluginRegistryEntry(registry, pluginId);
57179
+ if (!entry) {
57180
+ const message = `Restty plugin ${pluginId} was not found in registry`;
57181
+ this.setPluginLoadError(pluginId, message);
57182
+ results.push({ id: pluginId, status: "missing", error: message });
57183
+ continue;
57184
+ }
57185
+ let plugin;
57186
+ try {
57187
+ plugin = await this.resolvePluginRegistryEntry(entry);
57188
+ } catch (error) {
57189
+ const message = this.errorToMessage(error);
57190
+ this.setPluginLoadError(pluginId, message);
57191
+ results.push({ id: pluginId, status: "failed", error: message });
57192
+ continue;
57193
+ }
57194
+ const resolvedId = plugin.id?.trim?.() ?? "";
57195
+ if (resolvedId !== pluginId) {
57196
+ const message = `Restty plugin registry entry ${pluginId} resolved to id ${resolvedId || "(empty)"}`;
57197
+ this.setPluginLoadError(pluginId, message);
57198
+ results.push({ id: pluginId, status: "failed", error: message });
57199
+ continue;
57200
+ }
57201
+ try {
57202
+ await this.use(plugin, item.options);
57203
+ results.push({ id: pluginId, status: "loaded", error: null });
57204
+ } catch (error) {
57205
+ results.push({
57206
+ id: pluginId,
57207
+ status: "failed",
57208
+ error: this.errorToMessage(error)
57209
+ });
57210
+ }
57211
+ }
57212
+ return results;
57213
+ }
57214
+ unuse(pluginId) {
57215
+ const key = pluginId?.trim?.() ?? "";
57216
+ if (!key)
57217
+ return false;
57218
+ const runtime2 = this.pluginRuntimes.get(key);
57219
+ if (!runtime2)
57220
+ return false;
57221
+ this.pluginRuntimes.delete(key);
57222
+ this.teardownPluginRuntime(runtime2);
57223
+ this.updatePluginDiagnostic(key, {
57224
+ active: false,
57225
+ activatedAt: null
57226
+ });
57227
+ this.emitPluginEvent("plugin:deactivated", { pluginId: key });
57228
+ return true;
57229
+ }
57230
+ plugins() {
57231
+ return Array.from(this.pluginRuntimes.keys());
57232
+ }
57233
+ pluginInfo(pluginId) {
57234
+ if (typeof pluginId === "string") {
57235
+ const key = pluginId.trim();
57236
+ if (!key)
57237
+ return null;
57238
+ return this.buildPluginInfo(key);
57239
+ }
57240
+ const keys = new Set;
57241
+ for (const key of this.pluginDiagnostics.keys())
57242
+ keys.add(key);
57243
+ for (const key of this.pluginRuntimes.keys())
57244
+ keys.add(key);
57245
+ return Array.from(keys).sort((a3, b3) => a3.localeCompare(b3)).map((key) => this.buildPluginInfo(key)).filter((entry) => entry !== null);
57246
+ }
56688
57247
  destroy() {
57248
+ const pluginIds = this.plugins();
57249
+ for (let i3 = 0;i3 < pluginIds.length; i3 += 1) {
57250
+ this.unuse(pluginIds[i3]);
57251
+ }
56689
57252
  this.paneManager.destroy();
56690
57253
  }
56691
57254
  connectPty(url = "") {
56692
- this.requireActivePaneHandle().connectPty(url);
57255
+ const pane = this.requireActivePaneHandle();
57256
+ this.runLifecycleHooks({
57257
+ phase: "before",
57258
+ action: "connect-pty",
57259
+ paneId: pane.id
57260
+ });
57261
+ pane.connectPty(url);
57262
+ this.runLifecycleHooks({
57263
+ phase: "after",
57264
+ action: "connect-pty",
57265
+ paneId: pane.id,
57266
+ ok: true
57267
+ });
56693
57268
  }
56694
57269
  disconnectPty() {
56695
- this.requireActivePaneHandle().disconnectPty();
57270
+ const pane = this.requireActivePaneHandle();
57271
+ this.runLifecycleHooks({
57272
+ phase: "before",
57273
+ action: "disconnect-pty",
57274
+ paneId: pane.id
57275
+ });
57276
+ pane.disconnectPty();
57277
+ this.runLifecycleHooks({
57278
+ phase: "after",
57279
+ action: "disconnect-pty",
57280
+ paneId: pane.id,
57281
+ ok: true
57282
+ });
56696
57283
  }
56697
57284
  isPtyConnected() {
56698
57285
  return this.requireActivePaneHandle().isPtyConnected();
@@ -56739,6 +57326,58 @@ class Restty {
56739
57326
  dumpAtlasForCodepoint(cp) {
56740
57327
  this.requireActivePaneHandle().dumpAtlasForCodepoint(cp);
56741
57328
  }
57329
+ resize(cols, rows) {
57330
+ const pane = this.requireActivePaneHandle();
57331
+ this.runLifecycleHooks({
57332
+ phase: "before",
57333
+ action: "resize",
57334
+ paneId: pane.id,
57335
+ cols,
57336
+ rows
57337
+ });
57338
+ pane.resize(cols, rows);
57339
+ this.runLifecycleHooks({
57340
+ phase: "after",
57341
+ action: "resize",
57342
+ paneId: pane.id,
57343
+ cols,
57344
+ rows,
57345
+ ok: true
57346
+ });
57347
+ this.emitPluginEvent("pane:resized", { paneId: pane.id, cols, rows });
57348
+ }
57349
+ focus() {
57350
+ const pane = this.requireActivePaneHandle();
57351
+ this.runLifecycleHooks({
57352
+ phase: "before",
57353
+ action: "focus",
57354
+ paneId: pane.id
57355
+ });
57356
+ pane.focus();
57357
+ this.runLifecycleHooks({
57358
+ phase: "after",
57359
+ action: "focus",
57360
+ paneId: pane.id,
57361
+ ok: true
57362
+ });
57363
+ this.emitPluginEvent("pane:focused", { paneId: pane.id });
57364
+ }
57365
+ blur() {
57366
+ const pane = this.requireActivePaneHandle();
57367
+ this.runLifecycleHooks({
57368
+ phase: "before",
57369
+ action: "blur",
57370
+ paneId: pane.id
57371
+ });
57372
+ pane.blur();
57373
+ this.runLifecycleHooks({
57374
+ phase: "after",
57375
+ action: "blur",
57376
+ paneId: pane.id,
57377
+ ok: true
57378
+ });
57379
+ this.emitPluginEvent("pane:blurred", { paneId: pane.id });
57380
+ }
56742
57381
  updateSize(force) {
56743
57382
  this.requireActivePaneHandle().updateSize(force);
56744
57383
  }
@@ -56761,9 +57400,300 @@ class Restty {
56761
57400
  }
56762
57401
  return this.makePaneHandle(pane.id);
56763
57402
  }
57403
+ createPluginContext(runtime2) {
57404
+ return {
57405
+ restty: this,
57406
+ options: runtime2.options,
57407
+ panes: () => this.panes(),
57408
+ pane: (id) => this.pane(id),
57409
+ activePane: () => this.activePane(),
57410
+ focusedPane: () => this.focusedPane(),
57411
+ on: (event, listener) => {
57412
+ return {
57413
+ dispose: this.attachRuntimeDisposer(runtime2, "event", this.onPluginEvent(event, listener))
57414
+ };
57415
+ },
57416
+ addInputInterceptor: (interceptor, options) => {
57417
+ return {
57418
+ dispose: this.attachRuntimeDisposer(runtime2, "input-interceptor", this.addInputInterceptor(runtime2.plugin.id, interceptor, options))
57419
+ };
57420
+ },
57421
+ addOutputInterceptor: (interceptor, options) => {
57422
+ return {
57423
+ dispose: this.attachRuntimeDisposer(runtime2, "output-interceptor", this.addOutputInterceptor(runtime2.plugin.id, interceptor, options))
57424
+ };
57425
+ },
57426
+ addLifecycleHook: (hook, options) => {
57427
+ return {
57428
+ dispose: this.attachRuntimeDisposer(runtime2, "lifecycle-hook", this.addLifecycleHook(runtime2.plugin.id, hook, options))
57429
+ };
57430
+ },
57431
+ addRenderHook: (hook, options) => {
57432
+ return {
57433
+ dispose: this.attachRuntimeDisposer(runtime2, "render-hook", this.addRenderHook(runtime2.plugin.id, hook, options))
57434
+ };
57435
+ }
57436
+ };
57437
+ }
57438
+ attachRuntimeDisposer(runtime2, kind, dispose) {
57439
+ const entry = {
57440
+ kind,
57441
+ active: true,
57442
+ dispose: () => {
57443
+ if (!entry.active)
57444
+ return;
57445
+ entry.active = false;
57446
+ dispose();
57447
+ }
57448
+ };
57449
+ runtime2.disposers.push(entry);
57450
+ return entry.dispose;
57451
+ }
57452
+ addInputInterceptor(pluginId, interceptor, options) {
57453
+ return this.registerInterceptor(this.inputInterceptors, pluginId, interceptor, options);
57454
+ }
57455
+ addOutputInterceptor(pluginId, interceptor, options) {
57456
+ return this.registerInterceptor(this.outputInterceptors, pluginId, interceptor, options);
57457
+ }
57458
+ addLifecycleHook(pluginId, hook, options) {
57459
+ return this.registerInterceptor(this.lifecycleHooks, pluginId, hook, options);
57460
+ }
57461
+ addRenderHook(pluginId, hook, options) {
57462
+ return this.registerInterceptor(this.renderHooks, pluginId, hook, options);
57463
+ }
57464
+ registerInterceptor(bucket, pluginId, interceptor, options) {
57465
+ const entry = {
57466
+ id: this.nextInterceptorId++,
57467
+ pluginId,
57468
+ priority: Number.isFinite(options?.priority) ? Number(options?.priority) : 0,
57469
+ order: this.nextInterceptorOrder++,
57470
+ interceptor
57471
+ };
57472
+ bucket.push(entry);
57473
+ bucket.sort((a3, b3) => {
57474
+ if (a3.priority !== b3.priority)
57475
+ return a3.priority - b3.priority;
57476
+ return a3.order - b3.order;
57477
+ });
57478
+ return () => {
57479
+ const index = bucket.findIndex((current) => current.id === entry.id);
57480
+ if (index >= 0) {
57481
+ bucket.splice(index, 1);
57482
+ }
57483
+ };
57484
+ }
57485
+ applyInputInterceptors(paneId, text, source) {
57486
+ return this.applyInterceptors(this.inputInterceptors, "input", { paneId, text, source });
57487
+ }
57488
+ applyOutputInterceptors(paneId, text, source) {
57489
+ return this.applyInterceptors(this.outputInterceptors, "output", { paneId, text, source });
57490
+ }
57491
+ runLifecycleHooks(payload) {
57492
+ this.runHooks(this.lifecycleHooks, "lifecycle", payload);
57493
+ }
57494
+ runRenderHooks(payload) {
57495
+ this.runHooks(this.renderHooks, "render", payload);
57496
+ }
57497
+ applyInterceptors(bucket, kind, payload) {
57498
+ let currentText = payload.text;
57499
+ for (let i3 = 0;i3 < bucket.length; i3 += 1) {
57500
+ const entry = bucket[i3];
57501
+ try {
57502
+ const result = entry.interceptor({ ...payload, text: currentText });
57503
+ if (result === null)
57504
+ return null;
57505
+ if (typeof result === "string")
57506
+ currentText = result;
57507
+ } catch (error) {
57508
+ console.error(`[restty plugin] ${kind} interceptor error (${entry.pluginId}):`, error);
57509
+ }
57510
+ }
57511
+ return currentText;
57512
+ }
57513
+ runHooks(bucket, kind, payload) {
57514
+ for (let i3 = 0;i3 < bucket.length; i3 += 1) {
57515
+ const entry = bucket[i3];
57516
+ try {
57517
+ entry.interceptor(payload);
57518
+ } catch (error) {
57519
+ console.error(`[restty plugin] ${kind} hook error (${entry.pluginId}):`, error);
57520
+ }
57521
+ }
57522
+ }
57523
+ normalizePluginMetadata(plugin, pluginId) {
57524
+ return {
57525
+ ...plugin,
57526
+ id: pluginId,
57527
+ version: plugin.version?.trim?.() || undefined,
57528
+ apiVersion: Number.isFinite(plugin.apiVersion) ? Math.trunc(Number(plugin.apiVersion)) : undefined,
57529
+ requires: plugin.requires ?? undefined
57530
+ };
57531
+ }
57532
+ assertPluginCompatibility(pluginId, plugin) {
57533
+ const version = plugin.version?.trim?.();
57534
+ if (version !== undefined && !version) {
57535
+ throw new Error(`Restty plugin ${pluginId} has an empty version`);
57536
+ }
57537
+ if (plugin.apiVersion !== undefined) {
57538
+ if (!Number.isInteger(plugin.apiVersion) || plugin.apiVersion < 1) {
57539
+ throw new Error(`Restty plugin ${pluginId} has invalid apiVersion ${String(plugin.apiVersion)}`);
57540
+ }
57541
+ if (plugin.apiVersion !== RESTTY_PLUGIN_API_VERSION) {
57542
+ throw new Error(`Restty plugin ${pluginId} requires apiVersion ${plugin.apiVersion}, current is ${RESTTY_PLUGIN_API_VERSION}`);
57543
+ }
57544
+ }
57545
+ const requirement = plugin.requires?.pluginApi;
57546
+ if (requirement === undefined)
57547
+ return;
57548
+ if (typeof requirement === "number") {
57549
+ if (!Number.isInteger(requirement) || requirement < 1) {
57550
+ throw new Error(`Restty plugin ${pluginId} has invalid requires.pluginApi value`);
57551
+ }
57552
+ if (requirement !== RESTTY_PLUGIN_API_VERSION) {
57553
+ throw new Error(`Restty plugin ${pluginId} requires pluginApi ${requirement}, current is ${RESTTY_PLUGIN_API_VERSION}`);
57554
+ }
57555
+ return;
57556
+ }
57557
+ const min = requirement.min;
57558
+ const max = requirement.max;
57559
+ if (!Number.isInteger(min) || min < 1) {
57560
+ throw new Error(`Restty plugin ${pluginId} has invalid requires.pluginApi.min`);
57561
+ }
57562
+ if (max !== undefined && (!Number.isInteger(max) || max < min)) {
57563
+ throw new Error(`Restty plugin ${pluginId} has invalid requires.pluginApi.max`);
57564
+ }
57565
+ if (RESTTY_PLUGIN_API_VERSION < min || max !== undefined && RESTTY_PLUGIN_API_VERSION > max) {
57566
+ const range = max === undefined ? `>=${min}` : `${min}-${max}`;
57567
+ throw new Error(`Restty plugin ${pluginId} requires pluginApi range ${range}, current is ${RESTTY_PLUGIN_API_VERSION}`);
57568
+ }
57569
+ }
57570
+ lookupPluginRegistryEntry(registry, pluginId) {
57571
+ if (registry instanceof Map) {
57572
+ return registry.get(pluginId) ?? null;
57573
+ }
57574
+ if (Object.prototype.hasOwnProperty.call(registry, pluginId)) {
57575
+ return registry[pluginId];
57576
+ }
57577
+ return null;
57578
+ }
57579
+ async resolvePluginRegistryEntry(entry) {
57580
+ if (typeof entry === "function") {
57581
+ return await entry();
57582
+ }
57583
+ return entry;
57584
+ }
57585
+ setPluginLoadError(pluginId, message) {
57586
+ this.pluginDiagnostics.set(pluginId, {
57587
+ id: pluginId,
57588
+ version: null,
57589
+ apiVersion: null,
57590
+ requires: null,
57591
+ active: false,
57592
+ activatedAt: null,
57593
+ lastError: message
57594
+ });
57595
+ }
57596
+ updatePluginDiagnostic(pluginId, patch) {
57597
+ const current = this.pluginDiagnostics.get(pluginId);
57598
+ if (!current)
57599
+ return;
57600
+ this.pluginDiagnostics.set(pluginId, {
57601
+ ...current,
57602
+ ...patch
57603
+ });
57604
+ }
57605
+ buildPluginInfo(pluginId) {
57606
+ const diagnostic = this.pluginDiagnostics.get(pluginId) ?? null;
57607
+ const runtime2 = this.pluginRuntimes.get(pluginId) ?? null;
57608
+ if (!diagnostic && !runtime2)
57609
+ return null;
57610
+ const plugin = runtime2?.plugin;
57611
+ const listeners = runtime2 ? runtime2.disposers.filter((entry) => entry.active && entry.kind === "event").length : 0;
57612
+ const inputInterceptors = runtime2 ? runtime2.disposers.filter((entry) => entry.active && entry.kind === "input-interceptor").length : 0;
57613
+ const outputInterceptors = runtime2 ? runtime2.disposers.filter((entry) => entry.active && entry.kind === "output-interceptor").length : 0;
57614
+ const lifecycleHooks = runtime2 ? runtime2.disposers.filter((entry) => entry.active && entry.kind === "lifecycle-hook").length : 0;
57615
+ const renderHooks = runtime2 ? runtime2.disposers.filter((entry) => entry.active && entry.kind === "render-hook").length : 0;
57616
+ return {
57617
+ id: pluginId,
57618
+ version: plugin?.version?.trim?.() || diagnostic?.version || null,
57619
+ apiVersion: plugin?.apiVersion ?? (Number.isFinite(diagnostic?.apiVersion) ? diagnostic?.apiVersion : null),
57620
+ requires: plugin?.requires ?? diagnostic?.requires ?? null,
57621
+ active: runtime2 ? true : diagnostic?.active ?? false,
57622
+ activatedAt: runtime2?.activatedAt ?? diagnostic?.activatedAt ?? null,
57623
+ lastError: diagnostic?.lastError ?? null,
57624
+ listeners,
57625
+ inputInterceptors,
57626
+ outputInterceptors,
57627
+ lifecycleHooks,
57628
+ renderHooks
57629
+ };
57630
+ }
57631
+ errorToMessage(error) {
57632
+ if (error instanceof Error)
57633
+ return error.message || error.name || "Unknown error";
57634
+ return String(error);
57635
+ }
57636
+ onPluginEvent(event, listener) {
57637
+ let listeners = this.pluginListeners.get(event);
57638
+ if (!listeners) {
57639
+ listeners = new Set;
57640
+ this.pluginListeners.set(event, listeners);
57641
+ }
57642
+ const wrapped = listener;
57643
+ listeners.add(wrapped);
57644
+ return () => {
57645
+ const current = this.pluginListeners.get(event);
57646
+ if (!current)
57647
+ return;
57648
+ current.delete(wrapped);
57649
+ if (current.size === 0) {
57650
+ this.pluginListeners.delete(event);
57651
+ }
57652
+ };
57653
+ }
57654
+ emitPluginEvent(event, payload) {
57655
+ const listeners = this.pluginListeners.get(event);
57656
+ if (!listeners || listeners.size === 0)
57657
+ return;
57658
+ const snapshot = Array.from(listeners);
57659
+ for (let i3 = 0;i3 < snapshot.length; i3 += 1) {
57660
+ try {
57661
+ snapshot[i3](payload);
57662
+ } catch (error) {
57663
+ console.error(`[restty plugin] listener error (${String(event)}):`, error);
57664
+ }
57665
+ }
57666
+ }
57667
+ teardownPluginRuntime(runtime2) {
57668
+ for (let i3 = 0;i3 < runtime2.disposers.length; i3 += 1) {
57669
+ try {
57670
+ runtime2.disposers[i3].dispose();
57671
+ } catch {}
57672
+ }
57673
+ runtime2.disposers.length = 0;
57674
+ const cleanup = runtime2.cleanup;
57675
+ runtime2.cleanup = null;
57676
+ if (!cleanup)
57677
+ return;
57678
+ try {
57679
+ cleanup();
57680
+ } catch (error) {
57681
+ console.error(`[restty plugin] cleanup error (${runtime2.plugin.id}):`, error);
57682
+ }
57683
+ }
57684
+ normalizePluginCleanup(cleanup) {
57685
+ if (!cleanup)
57686
+ return null;
57687
+ if (typeof cleanup === "function")
57688
+ return cleanup;
57689
+ if (typeof cleanup === "object" && typeof cleanup.dispose === "function") {
57690
+ return () => cleanup.dispose();
57691
+ }
57692
+ return null;
57693
+ }
56764
57694
  }
56765
57695
  function createRestty(options) {
56766
57696
  return new Restty(options);
56767
57697
  }
56768
57698
 
56769
- export { BOX_LINE_MAP, BOX_STYLE_NONE, BOX_STYLE_LIGHT, BOX_STYLE_HEAVY, BOX_STYLE_DOUBLE, isPrivateUse, isSpaceCp, isBoxDrawing, isBlockElement, isLegacyComputing, isPowerline, isBraille, isGraphicsElement, isSymbolCp, applyAlpha, pushRect, pushRectSnapped, pushRectBox, drawBlockElement, drawBoxDrawing, drawBraille, drawPowerline, constrainGlyphBox, RECT_SHADER, GLYPH_SHADER, initWebGPUCore, initWebGPU, initWebGL, ensureInstanceBuffer, configureContext, ensureGLInstanceBuffer, createResizeState, createScrollbarState, ResttyWasm, loadResttyWasm, createInputHandler, NERD_SYMBOL_RANGES, isNerdSymbolCodepoint, fontHeightUnits, computeCellMetrics, createGridState, updateGridState, clamp, isSymbolFont, isNerdSymbolFont, fontMaxCellSpan, fontScaleOverride, fontRasterScale, createFontEntry, resetFontEntry, createFontManagerState, fontHasGlyph, fontAdvanceUnits, glyphWidthUnits, pickFontIndexForText, tryFetchFontBuffer, tryLocalFontBuffer, loadPrimaryFontBuffer, loadFallbackFontBuffers, NERD_CONSTRAINTS, getNerdConstraint, createPtyConnection, connectPty, disconnectPty, sendPtyInput, sendPtyResize, isPtyConnected, createWebSocketPtyTransport, parseGhosttyColor, colorToFloats, colorToRgbU32, parseGhosttyTheme, listBuiltinThemeNames, isBuiltinThemeName, getBuiltinThemeSource, getBuiltinTheme, createImeState, setPreedit, clearPreedit, startComposition, updateComposition, endComposition, syncImeSelection, updateImePosition, PREEDIT_BG, PREEDIT_ACTIVE_BG, PREEDIT_FG, PREEDIT_UL, PREEDIT_CARET, ResttyPaneHandle, Restty, createRestty };
57699
+ export { BOX_LINE_MAP, BOX_STYLE_NONE, BOX_STYLE_LIGHT, BOX_STYLE_HEAVY, BOX_STYLE_DOUBLE, isPrivateUse, isSpaceCp, isBoxDrawing, isBlockElement, isLegacyComputing, isPowerline, isBraille, isGraphicsElement, isSymbolCp, applyAlpha, pushRect, pushRectSnapped, pushRectBox, drawBlockElement, drawBoxDrawing, drawBraille, drawPowerline, constrainGlyphBox, RECT_SHADER, GLYPH_SHADER, initWebGPUCore, initWebGPU, initWebGL, ensureInstanceBuffer, configureContext, ensureGLInstanceBuffer, createResizeState, createScrollbarState, ResttyWasm, loadResttyWasm, createInputHandler, NERD_SYMBOL_RANGES, isNerdSymbolCodepoint, fontHeightUnits, computeCellMetrics, createGridState, updateGridState, clamp, isSymbolFont, isNerdSymbolFont, fontMaxCellSpan, fontScaleOverride, fontRasterScale, createFontEntry, resetFontEntry, createFontManagerState, fontHasGlyph, fontAdvanceUnits, glyphWidthUnits, pickFontIndexForText, tryFetchFontBuffer, tryLocalFontBuffer, loadPrimaryFontBuffer, loadFallbackFontBuffers, NERD_CONSTRAINTS, getNerdConstraint, createPtyConnection, connectPty, disconnectPty, sendPtyInput, sendPtyResize, isPtyConnected, createWebSocketPtyTransport, parseGhosttyColor, colorToFloats, colorToRgbU32, parseGhosttyTheme, listBuiltinThemeNames, isBuiltinThemeName, getBuiltinThemeSource, getBuiltinTheme, createImeState, setPreedit, clearPreedit, startComposition, updateComposition, endComposition, syncImeSelection, updateImePosition, PREEDIT_BG, PREEDIT_ACTIVE_BG, PREEDIT_FG, PREEDIT_UL, PREEDIT_CARET, RESTTY_PLUGIN_API_VERSION, ResttyPaneHandle, Restty, createRestty };