restty 0.1.22 → 0.1.24

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.
@@ -1,4 +1,4 @@
1
- import type { InputHandler, MouseMode } from "../input";
1
+ import type { DesktopNotification, InputHandler, MouseMode } from "../input";
2
2
  import type { GhosttyTheme } from "../theme";
3
3
  import { type CreateResttyAppPaneManagerOptions, type ResttyManagedAppPane, type ResttyManagedPaneStyleOptions, type ResttyPaneAppOptionsInput } from "./pane-app-manager";
4
4
  import type { ResttyPaneManager, ResttyPaneSplitDirection } from "./panes";
@@ -11,6 +11,10 @@ export type ResttyOptions = Omit<CreateResttyAppPaneManagerOptions, "appOptions"
11
11
  appOptions?: CreateResttyAppPaneManagerOptions["appOptions"];
12
12
  /** Font sources applied to every pane. */
13
13
  fontSources?: ResttyPaneAppOptionsInput["fontSources"];
14
+ /** Global handler for desktop notifications emitted by any pane. */
15
+ onDesktopNotification?: (notification: DesktopNotification & {
16
+ paneId: number;
17
+ }) => void;
14
18
  /** Whether to create the first pane automatically (default true). */
15
19
  createInitialPane?: boolean | {
16
20
  focus?: boolean;
@@ -1,4 +1,4 @@
1
- import type { InputHandler, MouseMode } from "../input";
1
+ import type { DesktopNotification, InputHandler, MouseMode } from "../input";
2
2
  import type { PtyTransport } from "../pty";
3
3
  import type { WebGPUCoreState } from "../renderer";
4
4
  import type { GhosttyTheme } from "../theme";
@@ -85,6 +85,8 @@ export type ResttyAppCallbacks = {
85
85
  onPtyStatus?: (status: string) => void;
86
86
  /** Mouse mode/status changed. */
87
87
  onMouseStatus?: (status: string) => void;
88
+ /** Terminal requested a desktop notification via OSC 9 / OSC 777. */
89
+ onDesktopNotification?: (notification: DesktopNotification) => void;
88
90
  };
89
91
  /** Raw font data as an ArrayBuffer or typed-array view. */
90
92
  export type ResttyFontBufferData = ArrayBuffer | ArrayBufferView;
@@ -8576,6 +8576,11 @@ class OutputFilter {
8576
8576
  clipboardWrite;
8577
8577
  clipboardRead;
8578
8578
  getDefaultColors;
8579
+ desktopNotificationHandler;
8580
+ semanticPromptSeen = false;
8581
+ promptClickEvents = false;
8582
+ promptInputActive = false;
8583
+ commandRunning = false;
8579
8584
  constructor(options) {
8580
8585
  this.getCursorPosition = options.getCursorPosition;
8581
8586
  this.sendReply = options.sendReply;
@@ -8585,6 +8590,7 @@ class OutputFilter {
8585
8590
  this.clipboardRead = options.onClipboardRead;
8586
8591
  this.windowOpHandler = options.onWindowOp;
8587
8592
  this.getWindowMetrics = options.getWindowMetrics;
8593
+ this.desktopNotificationHandler = options.onDesktopNotification;
8588
8594
  }
8589
8595
  setCursorProvider(fn) {
8590
8596
  this.getCursorPosition = fn;
@@ -8607,6 +8613,80 @@ class OutputFilter {
8607
8613
  isSynchronizedOutput() {
8608
8614
  return this.synchronizedOutput;
8609
8615
  }
8616
+ isPromptClickEventsEnabled() {
8617
+ return this.semanticPromptSeen && this.promptClickEvents && this.promptInputActive && !this.commandRunning && !this.altScreen;
8618
+ }
8619
+ encodePromptClickEvent(cell) {
8620
+ if (!this.isPromptClickEventsEnabled())
8621
+ return "";
8622
+ const row = Math.max(1, Math.floor(cell.row) + 1);
8623
+ const col = Math.max(1, Math.floor(cell.col) + 1);
8624
+ return `\x1B[<0;${col};${row}M`;
8625
+ }
8626
+ readOsc133BoolOption(options, key) {
8627
+ if (!options)
8628
+ return null;
8629
+ const prefix = `${key}=`;
8630
+ const fields = options.split(";");
8631
+ for (let i = 0;i < fields.length; i += 1) {
8632
+ const field = fields[i];
8633
+ if (!field.startsWith(prefix))
8634
+ continue;
8635
+ const value = field.slice(prefix.length);
8636
+ if (value === "1")
8637
+ return true;
8638
+ if (value === "0")
8639
+ return false;
8640
+ return null;
8641
+ }
8642
+ return null;
8643
+ }
8644
+ observeSemanticPromptOsc(action, options) {
8645
+ const clickEvents = this.readOsc133BoolOption(options, "click_events");
8646
+ if (clickEvents !== null)
8647
+ this.promptClickEvents = clickEvents;
8648
+ switch (action) {
8649
+ case "A":
8650
+ case "B":
8651
+ case "I":
8652
+ this.semanticPromptSeen = true;
8653
+ this.promptInputActive = true;
8654
+ this.commandRunning = false;
8655
+ break;
8656
+ case "C":
8657
+ this.semanticPromptSeen = true;
8658
+ this.promptInputActive = false;
8659
+ this.commandRunning = true;
8660
+ break;
8661
+ case "D":
8662
+ this.semanticPromptSeen = true;
8663
+ this.promptInputActive = false;
8664
+ this.commandRunning = false;
8665
+ break;
8666
+ case "P":
8667
+ this.semanticPromptSeen = true;
8668
+ break;
8669
+ default:
8670
+ break;
8671
+ }
8672
+ }
8673
+ observeOsc(seq) {
8674
+ const content = seq.slice(2);
8675
+ const sep = content.indexOf(";");
8676
+ if (sep < 0)
8677
+ return;
8678
+ const code = content.slice(0, sep);
8679
+ if (code !== "133")
8680
+ return;
8681
+ const rest = content.slice(sep + 1);
8682
+ if (!rest)
8683
+ return;
8684
+ const action = rest[0] ?? "";
8685
+ if (!action)
8686
+ return;
8687
+ const options = rest.length > 2 && rest[1] === ";" ? rest.slice(2) : "";
8688
+ this.observeSemanticPromptOsc(action, options);
8689
+ }
8610
8690
  replyOscColor(code, rgb) {
8611
8691
  const toHex4 = (value) => Math.round(Math.max(0, Math.min(255, value)) * 257).toString(16).padStart(4, "0");
8612
8692
  const r = toHex4(rgb[0]);
@@ -8618,6 +8698,39 @@ class OutputFilter {
8618
8698
  const content = seq.slice(2);
8619
8699
  const parts = content.split(";");
8620
8700
  const code = parts[0] ?? "";
8701
+ if (code === "9") {
8702
+ const firstSep = content.indexOf(";");
8703
+ const body = firstSep >= 0 ? content.slice(firstSep + 1) : "";
8704
+ if (/^(?:[2-9]|1[0-2]?)(?:;|$)/.test(body)) {
8705
+ return true;
8706
+ }
8707
+ this.desktopNotificationHandler?.({
8708
+ title: "",
8709
+ body,
8710
+ source: "osc9",
8711
+ raw: seq
8712
+ });
8713
+ return true;
8714
+ }
8715
+ if (code === "777") {
8716
+ const firstSep = content.indexOf(";");
8717
+ const rest = firstSep >= 0 ? content.slice(firstSep + 1) : "";
8718
+ if (!rest.startsWith("notify;")) {
8719
+ return true;
8720
+ }
8721
+ const payload = rest.slice("notify;".length);
8722
+ const titleSep = payload.indexOf(";");
8723
+ if (titleSep < 0) {
8724
+ return true;
8725
+ }
8726
+ this.desktopNotificationHandler?.({
8727
+ title: payload.slice(0, titleSep),
8728
+ body: payload.slice(titleSep + 1),
8729
+ source: "osc777",
8730
+ raw: seq
8731
+ });
8732
+ return true;
8733
+ }
8621
8734
  if (code === "52") {
8622
8735
  const target = parts[1] ?? "c";
8623
8736
  const payload = parts.slice(2).join(";");
@@ -8749,6 +8862,7 @@ class OutputFilter {
8749
8862
  break;
8750
8863
  }
8751
8864
  const seq2 = data.slice(i, j2);
8865
+ this.observeOsc(seq2);
8752
8866
  if (!this.handleOsc(seq2)) {
8753
8867
  result += data.slice(i, j2 + terminatorLen);
8754
8868
  }
@@ -8825,7 +8939,8 @@ function createInputHandler(options = {}) {
8825
8939
  onClipboardRead: options.onClipboardRead,
8826
8940
  onClipboardWrite: options.onClipboardWrite,
8827
8941
  onWindowOp: options.onWindowOp,
8828
- getWindowMetrics: options.getWindowMetrics
8942
+ getWindowMetrics: options.getWindowMetrics,
8943
+ onDesktopNotification: options.onDesktopNotification
8829
8944
  });
8830
8945
  return {
8831
8946
  sequences,
@@ -8858,6 +8973,8 @@ function createInputHandler(options = {}) {
8858
8973
  isFocusReporting: () => filter.isFocusReporting(),
8859
8974
  isAltScreen: () => filter.isAltScreen(),
8860
8975
  isSynchronizedOutput: () => filter.isSynchronizedOutput(),
8976
+ isPromptClickEventsEnabled: () => filter.isPromptClickEventsEnabled(),
8977
+ encodePromptClickEvent: (cell) => filter.encodePromptClickEvent(cell),
8861
8978
  sendMouseEvent: (kind, event) => mouse.sendMouseEvent(kind, event)
8862
8979
  };
8863
8980
  }
@@ -50571,6 +50688,11 @@ function createResttyApp(options) {
50571
50688
  panLastY: 0,
50572
50689
  pendingTimer: 0
50573
50690
  };
50691
+ const desktopSelectionState = {
50692
+ pendingPointerId: null,
50693
+ pendingCell: null,
50694
+ startedWithActiveSelection: false
50695
+ };
50574
50696
  const linkState = {
50575
50697
  hoverId: 0,
50576
50698
  hoverUri: ""
@@ -50590,11 +50712,7 @@ function createResttyApp(options) {
50590
50712
  function updateCanvasCursor() {
50591
50713
  if (!canvas)
50592
50714
  return;
50593
- if (selectionState.dragging || selectionState.active) {
50594
- canvas.style.cursor = "text";
50595
- return;
50596
- }
50597
- canvas.style.cursor = linkState.hoverId ? "pointer" : "default";
50715
+ canvas.style.cursor = "text";
50598
50716
  }
50599
50717
  function isTouchPointer(event) {
50600
50718
  return event.pointerType === "touch";
@@ -50608,6 +50726,11 @@ function createResttyApp(options) {
50608
50726
  touchSelectionState.pendingCell = null;
50609
50727
  touchSelectionState.pendingStartedAt = 0;
50610
50728
  }
50729
+ function clearPendingDesktopSelection() {
50730
+ desktopSelectionState.pendingPointerId = null;
50731
+ desktopSelectionState.pendingCell = null;
50732
+ desktopSelectionState.startedWithActiveSelection = false;
50733
+ }
50611
50734
  function tryActivatePendingTouchSelection(pointerId) {
50612
50735
  if (touchSelectionMode !== "long-press")
50613
50736
  return false;
@@ -50623,6 +50746,7 @@ function createResttyApp(options) {
50623
50746
  return true;
50624
50747
  }
50625
50748
  function beginSelectionDrag(cell, pointerId) {
50749
+ clearPendingDesktopSelection();
50626
50750
  selectionState.active = true;
50627
50751
  selectionState.dragging = true;
50628
50752
  selectionState.anchor = cell;
@@ -51420,10 +51544,12 @@ function createResttyApp(options) {
51420
51544
  },
51421
51545
  onWindowOp: (op) => {
51422
51546
  appendLog(`[term] window op ${op.type} ${op.params.join(";")}`);
51423
- }
51547
+ },
51548
+ onDesktopNotification: callbacks?.onDesktopNotification
51424
51549
  });
51425
51550
  inputHandler.setMouseMode("auto");
51426
51551
  function clearSelection() {
51552
+ clearPendingDesktopSelection();
51427
51553
  selectionState.active = false;
51428
51554
  selectionState.dragging = false;
51429
51555
  selectionState.anchor = null;
@@ -51683,6 +51809,7 @@ function createResttyApp(options) {
51683
51809
  }
51684
51810
  }
51685
51811
  if (shouldRoutePointerToAppMouse(event.shiftKey) && !shouldPreferLocalPrimarySelection(event) && inputHandler.sendMouseEvent("down", event)) {
51812
+ clearPendingDesktopSelection();
51686
51813
  event.preventDefault();
51687
51814
  canvas.setPointerCapture?.(event.pointerId);
51688
51815
  return;
@@ -51718,7 +51845,9 @@ function createResttyApp(options) {
51718
51845
  event.preventDefault();
51719
51846
  const cell = normalizeSelectionCell(positionToCell(event));
51720
51847
  updateLinkHover(cell);
51721
- beginSelectionDrag(cell, event.pointerId);
51848
+ desktopSelectionState.pendingPointerId = event.pointerId;
51849
+ desktopSelectionState.pendingCell = cell;
51850
+ desktopSelectionState.startedWithActiveSelection = selectionState.active;
51722
51851
  };
51723
51852
  const onPointerMove = (event) => {
51724
51853
  if (scrollbarDragState.pointerId === event.pointerId) {
@@ -51770,6 +51899,19 @@ function createResttyApp(options) {
51770
51899
  return;
51771
51900
  }
51772
51901
  const cell = normalizeSelectionCell(positionToCell(event));
51902
+ if (desktopSelectionState.pendingPointerId === event.pointerId && desktopSelectionState.pendingCell) {
51903
+ const anchor = desktopSelectionState.pendingCell;
51904
+ if (anchor.row !== cell.row || anchor.col !== cell.col) {
51905
+ beginSelectionDrag(anchor, event.pointerId);
51906
+ selectionState.focus = cell;
51907
+ updateLinkHover(null);
51908
+ updateCanvasCursor();
51909
+ needsRender = true;
51910
+ return;
51911
+ }
51912
+ updateLinkHover(cell);
51913
+ return;
51914
+ }
51773
51915
  if (selectionState.dragging) {
51774
51916
  event.preventDefault();
51775
51917
  selectionState.focus = cell;
@@ -51817,6 +51959,12 @@ function createResttyApp(options) {
51817
51959
  return;
51818
51960
  }
51819
51961
  const cell = normalizeSelectionCell(positionToCell(event));
51962
+ const clearSelectionFromClick = desktopSelectionState.pendingPointerId === event.pointerId && desktopSelectionState.startedWithActiveSelection && !selectionState.dragging;
51963
+ if (desktopSelectionState.pendingPointerId === event.pointerId) {
51964
+ clearPendingDesktopSelection();
51965
+ }
51966
+ if (clearSelectionFromClick)
51967
+ clearSelection();
51820
51968
  if (selectionState.dragging) {
51821
51969
  event.preventDefault();
51822
51970
  selectionState.dragging = false;
@@ -51834,6 +51982,14 @@ function createResttyApp(options) {
51834
51982
  }
51835
51983
  updateLinkHover(cell);
51836
51984
  }
51985
+ if (!selectionState.active && event.button === 0 && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey && inputHandler.isPromptClickEventsEnabled()) {
51986
+ const seq = inputHandler.encodePromptClickEvent(cell);
51987
+ if (seq) {
51988
+ event.preventDefault();
51989
+ sendKeyInput(seq);
51990
+ return;
51991
+ }
51992
+ }
51837
51993
  if (!selectionState.active && event.button === 0 && linkState.hoverUri) {
51838
51994
  openLink(linkState.hoverUri);
51839
51995
  }
@@ -51842,6 +51998,9 @@ function createResttyApp(options) {
51842
51998
  if (scrollbarDragState.pointerId === event.pointerId) {
51843
51999
  scrollbarDragState.pointerId = null;
51844
52000
  }
52001
+ if (desktopSelectionState.pendingPointerId === event.pointerId) {
52002
+ clearPendingDesktopSelection();
52003
+ }
51845
52004
  if (isTouchPointer(event)) {
51846
52005
  if (touchSelectionState.pendingPointerId === event.pointerId) {
51847
52006
  clearPendingTouchSelection();
@@ -56736,6 +56895,7 @@ class Restty {
56736
56895
  createInitialPane = true,
56737
56896
  appOptions,
56738
56897
  fontSources,
56898
+ onDesktopNotification,
56739
56899
  onPaneCreated,
56740
56900
  onPaneClosed,
56741
56901
  onPaneSplit,
@@ -56748,9 +56908,17 @@ class Restty {
56748
56908
  const resolved = typeof appOptions === "function" ? appOptions(context) : appOptions ?? {};
56749
56909
  const resolvedBeforeInput = resolved.beforeInput;
56750
56910
  const resolvedBeforeRenderOutput = resolved.beforeRenderOutput;
56911
+ const resolvedCallbacks = resolved.callbacks;
56751
56912
  return {
56752
56913
  ...resolved,
56753
56914
  ...this.fontSources ? { fontSources: this.fontSources } : {},
56915
+ callbacks: onDesktopNotification || resolvedCallbacks?.onDesktopNotification ? {
56916
+ ...resolvedCallbacks,
56917
+ onDesktopNotification: (notification) => {
56918
+ resolvedCallbacks?.onDesktopNotification?.(notification);
56919
+ onDesktopNotification?.({ ...notification, paneId: context.id });
56920
+ }
56921
+ } : resolvedCallbacks,
56754
56922
  beforeInput: ({ text, source }) => {
56755
56923
  const maybeUserText = resolvedBeforeInput?.({ text, source });
56756
56924
  if (maybeUserText === null)
@@ -3,4 +3,4 @@ import type { InputHandler, InputHandlerOptions } from "./types";
3
3
  * Create a terminal input handler with key, IME, PTY, and mouse support.
4
4
  */
5
5
  export declare function createInputHandler(options?: InputHandlerOptions): InputHandler;
6
- export type { CellPosition, CursorPosition, InputHandler, InputHandlerConfig, InputHandlerOptions, MouseMode, MouseStatus, } from "./types";
6
+ export type { CellPosition, CursorPosition, InputHandler, InputHandlerConfig, InputHandlerOptions, MouseMode, MouseStatus, DesktopNotification, } from "./types";
@@ -1,4 +1,4 @@
1
- import type { CursorPosition, WindowOp } from "./types";
1
+ import type { CellPosition, CursorPosition, DesktopNotification, WindowOp } from "./types";
2
2
  import type { MouseController } from "./mouse";
3
3
  /**
4
4
  * Construction options for OutputFilter.
@@ -31,6 +31,8 @@ export type OutputFilterOptions = {
31
31
  cellWidthPx: number;
32
32
  cellHeightPx: number;
33
33
  };
34
+ /** Handler for desktop notifications (OSC 9 / OSC 777). */
35
+ onDesktopNotification?: (notification: DesktopNotification) => void;
34
36
  };
35
37
  /**
36
38
  * Parses output for control queries (CPR/DA) and mouse mode toggles,
@@ -50,6 +52,11 @@ export declare class OutputFilter {
50
52
  private clipboardWrite?;
51
53
  private clipboardRead?;
52
54
  private getDefaultColors?;
55
+ private desktopNotificationHandler?;
56
+ private semanticPromptSeen;
57
+ private promptClickEvents;
58
+ private promptInputActive;
59
+ private commandRunning;
53
60
  constructor(options: OutputFilterOptions);
54
61
  setCursorProvider(fn: () => CursorPosition): void;
55
62
  setReplySink(fn: (data: string) => void): void;
@@ -58,6 +65,11 @@ export declare class OutputFilter {
58
65
  isBracketedPaste(): boolean;
59
66
  isFocusReporting(): boolean;
60
67
  isSynchronizedOutput(): boolean;
68
+ isPromptClickEventsEnabled(): boolean;
69
+ encodePromptClickEvent(cell: CellPosition): string;
70
+ private readOsc133BoolOption;
71
+ private observeSemanticPromptOsc;
72
+ private observeOsc;
61
73
  private replyOscColor;
62
74
  private handleOsc;
63
75
  private handleModeSeq;
@@ -59,6 +59,15 @@ export type WindowOp = {
59
59
  params: number[];
60
60
  raw: string;
61
61
  };
62
+ /**
63
+ * Desktop notification payload parsed from terminal OSC sequences.
64
+ */
65
+ export type DesktopNotification = {
66
+ title: string;
67
+ body: string;
68
+ source: "osc9" | "osc777";
69
+ raw: string;
70
+ };
62
71
  /**
63
72
  * Input handler construction options.
64
73
  */
@@ -103,6 +112,10 @@ export type InputHandlerOptions = {
103
112
  */
104
113
  onClipboardWrite?: (text: string) => void | Promise<void>;
105
114
  onClipboardRead?: () => string | null | Promise<string | null>;
115
+ /**
116
+ * Optional handler for desktop notifications (OSC 9 / OSC 777).
117
+ */
118
+ onDesktopNotification?: (notification: DesktopNotification) => void;
106
119
  /**
107
120
  * Return active Kitty keyboard protocol flags (CSI ? u query result).
108
121
  */
@@ -160,6 +173,8 @@ export type InputHandler = {
160
173
  isFocusReporting: () => boolean;
161
174
  isAltScreen: () => boolean;
162
175
  isSynchronizedOutput: () => boolean;
176
+ isPromptClickEventsEnabled: () => boolean;
177
+ encodePromptClickEvent: (cell: CellPosition) => string;
163
178
  /**
164
179
  * Encode pointer input as terminal mouse events (SGR).
165
180
  */
package/dist/internal.js CHANGED
@@ -89,7 +89,7 @@ import {
89
89
  updateComposition,
90
90
  updateGridState,
91
91
  updateImePosition
92
- } from "./chunk-d3xkkgrb.js";
92
+ } from "./chunk-68qdtczc.js";
93
93
  // src/selection/selection.ts
94
94
  function createSelectionState() {
95
95
  return {
package/dist/restty.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  isBuiltinThemeName,
9
9
  listBuiltinThemeNames,
10
10
  parseGhosttyTheme
11
- } from "./chunk-d3xkkgrb.js";
11
+ } from "./chunk-68qdtczc.js";
12
12
  export {
13
13
  parseGhosttyTheme,
14
14
  listBuiltinThemeNames,
package/dist/xterm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRestty
3
- } from "./chunk-d3xkkgrb.js";
3
+ } from "./chunk-68qdtczc.js";
4
4
 
5
5
  // src/xterm.ts
6
6
  class Terminal {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "restty",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Browser terminal rendering library powered by WASM, WebGPU/WebGL2, and TypeScript text shaping.",
5
5
  "keywords": [
6
6
  "terminal",