@vibecook/ghosttea-react 0.2.0

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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/TerminalSurface.d.ts +28 -0
  4. package/dist/TerminalSurface.d.ts.map +1 -0
  5. package/dist/TerminalSurface.js +614 -0
  6. package/dist/TerminalSurface.js.map +1 -0
  7. package/dist/bindings/action-route.d.ts +155 -0
  8. package/dist/bindings/action-route.d.ts.map +1 -0
  9. package/dist/bindings/action-route.js +196 -0
  10. package/dist/bindings/action-route.js.map +1 -0
  11. package/dist/bindings/fixtures/extensions.json +10 -0
  12. package/dist/bindings/fixtures/keybinds-linux-default.json +505 -0
  13. package/dist/bindings/fixtures/keybinds-macos-default.json +660 -0
  14. package/dist/bindings/ghostty-actions.d.ts +247 -0
  15. package/dist/bindings/ghostty-actions.d.ts.map +1 -0
  16. package/dist/bindings/ghostty-actions.js +431 -0
  17. package/dist/bindings/ghostty-actions.js.map +1 -0
  18. package/dist/bindings/ghostty-bindings.d.ts +88 -0
  19. package/dist/bindings/ghostty-bindings.d.ts.map +1 -0
  20. package/dist/bindings/ghostty-bindings.js +122 -0
  21. package/dist/bindings/ghostty-bindings.js.map +1 -0
  22. package/dist/bindings/ghostty-triggers.d.ts +47 -0
  23. package/dist/bindings/ghostty-triggers.d.ts.map +1 -0
  24. package/dist/bindings/ghostty-triggers.js +378 -0
  25. package/dist/bindings/ghostty-triggers.js.map +1 -0
  26. package/dist/bindings/index.d.ts +5 -0
  27. package/dist/bindings/index.d.ts.map +1 -0
  28. package/dist/bindings/index.js +5 -0
  29. package/dist/bindings/index.js.map +1 -0
  30. package/dist/catalog-budget.d.ts +22 -0
  31. package/dist/catalog-budget.d.ts.map +1 -0
  32. package/dist/catalog-budget.js +53 -0
  33. package/dist/catalog-budget.js.map +1 -0
  34. package/dist/cell-width.d.ts +3 -0
  35. package/dist/cell-width.d.ts.map +1 -0
  36. package/dist/cell-width.js +19 -0
  37. package/dist/cell-width.js.map +1 -0
  38. package/dist/context.d.ts +9 -0
  39. package/dist/context.d.ts.map +1 -0
  40. package/dist/context.js +13 -0
  41. package/dist/context.js.map +1 -0
  42. package/dist/cursor-invalidation.d.ts +3 -0
  43. package/dist/cursor-invalidation.d.ts.map +1 -0
  44. package/dist/cursor-invalidation.js +4 -0
  45. package/dist/cursor-invalidation.js.map +1 -0
  46. package/dist/frame-resync.d.ts +16 -0
  47. package/dist/frame-resync.d.ts.map +1 -0
  48. package/dist/frame-resync.js +164 -0
  49. package/dist/frame-resync.js.map +1 -0
  50. package/dist/frame-sequence.d.ts +14 -0
  51. package/dist/frame-sequence.d.ts.map +1 -0
  52. package/dist/frame-sequence.js +16 -0
  53. package/dist/frame-sequence.js.map +1 -0
  54. package/dist/index.d.ts +6 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +5 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/keyboard-input.d.ts +18 -0
  59. package/dist/keyboard-input.d.ts.map +1 -0
  60. package/dist/keyboard-input.js +101 -0
  61. package/dist/keyboard-input.js.map +1 -0
  62. package/dist/performance.d.ts +66 -0
  63. package/dist/performance.d.ts.map +1 -0
  64. package/dist/performance.js +21 -0
  65. package/dist/performance.js.map +1 -0
  66. package/dist/renderers/canvas-renderer.d.ts +12 -0
  67. package/dist/renderers/canvas-renderer.d.ts.map +1 -0
  68. package/dist/renderers/canvas-renderer.js +117 -0
  69. package/dist/renderers/canvas-renderer.js.map +1 -0
  70. package/dist/renderers/render-damage.d.ts +7 -0
  71. package/dist/renderers/render-damage.d.ts.map +1 -0
  72. package/dist/renderers/render-damage.js +16 -0
  73. package/dist/renderers/render-damage.js.map +1 -0
  74. package/dist/renderers/types.d.ts +65 -0
  75. package/dist/renderers/types.d.ts.map +1 -0
  76. package/dist/renderers/types.js +31 -0
  77. package/dist/renderers/types.js.map +1 -0
  78. package/dist/renderers/webgpu-renderer.d.ts +27 -0
  79. package/dist/renderers/webgpu-renderer.d.ts.map +1 -0
  80. package/dist/renderers/webgpu-renderer.js +1746 -0
  81. package/dist/renderers/webgpu-renderer.js.map +1 -0
  82. package/dist/runtime.d.ts +70 -0
  83. package/dist/runtime.d.ts.map +1 -0
  84. package/dist/runtime.js +1021 -0
  85. package/dist/runtime.js.map +1 -0
  86. package/dist/scroll-input.d.ts +6 -0
  87. package/dist/scroll-input.d.ts.map +1 -0
  88. package/dist/scroll-input.js +14 -0
  89. package/dist/scroll-input.js.map +1 -0
  90. package/dist/selection-input.d.ts +16 -0
  91. package/dist/selection-input.d.ts.map +1 -0
  92. package/dist/selection-input.js +67 -0
  93. package/dist/selection-input.js.map +1 -0
  94. package/dist/selection-text.d.ts +4 -0
  95. package/dist/selection-text.d.ts.map +1 -0
  96. package/dist/selection-text.js +28 -0
  97. package/dist/selection-text.js.map +1 -0
  98. package/dist/styles.css +75 -0
  99. package/dist/terminal-bindings.d.ts +22 -0
  100. package/dist/terminal-bindings.d.ts.map +1 -0
  101. package/dist/terminal-bindings.js +22 -0
  102. package/dist/terminal-bindings.js.map +1 -0
  103. package/dist/terminal-render.worker.d.ts +2 -0
  104. package/dist/terminal-render.worker.d.ts.map +1 -0
  105. package/dist/terminal-render.worker.js +3165 -0
  106. package/dist/terminal-render.worker.js.map +7 -0
  107. package/dist/worker-messages.d.ts +111 -0
  108. package/dist/worker-messages.d.ts.map +1 -0
  109. package/dist/worker-messages.js +2 -0
  110. package/dist/worker-messages.js.map +1 -0
  111. package/dist/workspace/RemoteSessionPalette.d.ts +12 -0
  112. package/dist/workspace/RemoteSessionPalette.d.ts.map +1 -0
  113. package/dist/workspace/RemoteSessionPalette.js +135 -0
  114. package/dist/workspace/RemoteSessionPalette.js.map +1 -0
  115. package/dist/workspace/Workspace.d.ts +69 -0
  116. package/dist/workspace/Workspace.d.ts.map +1 -0
  117. package/dist/workspace/Workspace.js +536 -0
  118. package/dist/workspace/Workspace.js.map +1 -0
  119. package/dist/workspace/hotkeys.d.ts +27 -0
  120. package/dist/workspace/hotkeys.d.ts.map +1 -0
  121. package/dist/workspace/hotkeys.js +29 -0
  122. package/dist/workspace/hotkeys.js.map +1 -0
  123. package/dist/workspace/index.d.ts +8 -0
  124. package/dist/workspace/index.d.ts.map +1 -0
  125. package/dist/workspace/index.js +8 -0
  126. package/dist/workspace/index.js.map +1 -0
  127. package/dist/workspace/pane-layout.d.ts +35 -0
  128. package/dist/workspace/pane-layout.d.ts.map +1 -0
  129. package/dist/workspace/pane-layout.js +139 -0
  130. package/dist/workspace/pane-layout.js.map +1 -0
  131. package/dist/workspace/pending-cache.d.ts +5 -0
  132. package/dist/workspace/pending-cache.d.ts.map +1 -0
  133. package/dist/workspace/pending-cache.js +22 -0
  134. package/dist/workspace/pending-cache.js.map +1 -0
  135. package/dist/workspace/session-scope.d.ts +3 -0
  136. package/dist/workspace/session-scope.d.ts.map +1 -0
  137. package/dist/workspace/session-scope.js +6 -0
  138. package/dist/workspace/session-scope.js.map +1 -0
  139. package/dist/workspace/themes.d.ts +18 -0
  140. package/dist/workspace/themes.d.ts.map +1 -0
  141. package/dist/workspace/themes.js +19 -0
  142. package/dist/workspace/themes.js.map +1 -0
  143. package/dist/workspace/workspace-model.d.ts +59 -0
  144. package/dist/workspace/workspace-model.d.ts.map +1 -0
  145. package/dist/workspace/workspace-model.js +257 -0
  146. package/dist/workspace/workspace-model.js.map +1 -0
  147. package/dist/workspace/workspace-tabs.d.ts +41 -0
  148. package/dist/workspace/workspace-tabs.d.ts.map +1 -0
  149. package/dist/workspace/workspace-tabs.js +133 -0
  150. package/dist/workspace/workspace-tabs.js.map +1 -0
  151. package/dist/workspace.css +453 -0
  152. package/package.json +75 -0
@@ -0,0 +1,3165 @@
1
+ // ../terminal-frame/dist/index.js
2
+ var FRAME_MAGIC = 826692180;
3
+ var FRAME_PROTOCOL_VERSION = 1;
4
+ var FRAME_HEADER_BYTES = 64;
5
+ var SECTION_HEADER_BYTES = 16;
6
+ var MAX_FRAME_BYTES = 16 * 1024 * 1024;
7
+ var FrameFlag = {
8
+ FullSnapshot: 1 << 0,
9
+ MouseTracking: 1 << 1,
10
+ CatalogReset: 1 << 2
11
+ };
12
+ var SectionKind;
13
+ (function(SectionKind2) {
14
+ SectionKind2[SectionKind2["GlyphDefinitions"] = 1] = "GlyphDefinitions";
15
+ SectionKind2[SectionKind2["StyleDefinitions"] = 2] = "StyleDefinitions";
16
+ SectionKind2[SectionKind2["RowReplacements"] = 3] = "RowReplacements";
17
+ SectionKind2[SectionKind2["CursorState"] = 4] = "CursorState";
18
+ SectionKind2[SectionKind2["SelectionSpans"] = 5] = "SelectionSpans";
19
+ SectionKind2[SectionKind2["ImageDefinitions"] = 6] = "ImageDefinitions";
20
+ SectionKind2[SectionKind2["ImagePlacements"] = 7] = "ImagePlacements";
21
+ SectionKind2[SectionKind2["ScrollbarState"] = 8] = "ScrollbarState";
22
+ SectionKind2[SectionKind2["ViewportMetadata"] = 9] = "ViewportMetadata";
23
+ SectionKind2[SectionKind2["AccessibilityText"] = 10] = "AccessibilityText";
24
+ SectionKind2[SectionKind2["ClipboardWrite"] = 11] = "ClipboardWrite";
25
+ })(SectionKind || (SectionKind = {}));
26
+ var GlyphFormat;
27
+ (function(GlyphFormat2) {
28
+ GlyphFormat2[GlyphFormat2["Alpha8"] = 0] = "Alpha8";
29
+ GlyphFormat2[GlyphFormat2["Rgba8Premultiplied"] = 1] = "Rgba8Premultiplied";
30
+ })(GlyphFormat || (GlyphFormat = {}));
31
+ var CursorStyle;
32
+ (function(CursorStyle2) {
33
+ CursorStyle2[CursorStyle2["Bar"] = 0] = "Bar";
34
+ CursorStyle2[CursorStyle2["Block"] = 1] = "Block";
35
+ CursorStyle2[CursorStyle2["Underline"] = 2] = "Underline";
36
+ CursorStyle2[CursorStyle2["HollowBlock"] = 3] = "HollowBlock";
37
+ })(CursorStyle || (CursorStyle = {}));
38
+ function assertRange(condition, message) {
39
+ if (!condition)
40
+ throw new RangeError(`Invalid terminal frame: ${message}`);
41
+ }
42
+ function decodeFrame(buffer) {
43
+ assertRange(buffer.byteLength >= FRAME_HEADER_BYTES, "truncated header");
44
+ assertRange(buffer.byteLength <= MAX_FRAME_BYTES, "packet exceeds limit");
45
+ const view = new DataView(buffer);
46
+ assertRange(view.getUint32(0, true) === FRAME_MAGIC, "bad magic");
47
+ assertRange(view.getUint16(4, true) === FRAME_PROTOCOL_VERSION, "unsupported protocol version");
48
+ const sectionCount = view.getUint16(60, true);
49
+ const tableEnd = FRAME_HEADER_BYTES + sectionCount * SECTION_HEADER_BYTES;
50
+ assertRange(tableEnd <= buffer.byteLength, "truncated section table");
51
+ const sections = [];
52
+ for (let index = 0; index < sectionCount; index += 1) {
53
+ const base = FRAME_HEADER_BYTES + index * SECTION_HEADER_BYTES;
54
+ const kind = view.getUint16(base, true);
55
+ const flags = view.getUint16(base + 2, true);
56
+ const offset = view.getUint32(base + 4, true);
57
+ const length = view.getUint32(base + 8, true);
58
+ const itemCount = view.getUint32(base + 12, true);
59
+ const end = offset + length;
60
+ assertRange(end >= offset && offset >= tableEnd && end <= buffer.byteLength, "section out of bounds");
61
+ sections.push({ kind, flags, itemCount, bytes: new Uint8Array(buffer, offset, length) });
62
+ }
63
+ return {
64
+ protocolVersion: view.getUint16(4, true),
65
+ flags: view.getUint16(6, true),
66
+ sessionHandle: view.getBigUint64(8, true),
67
+ viewHandle: view.getBigUint64(16, true),
68
+ sessionEpoch: view.getBigUint64(24, true),
69
+ layoutEpoch: view.getBigUint64(32, true),
70
+ frameSequence: view.getBigUint64(40, true),
71
+ terminalRevision: view.getBigUint64(48, true),
72
+ cols: view.getUint16(56, true),
73
+ rows: view.getUint16(58, true),
74
+ sections
75
+ };
76
+ }
77
+ function decodeRowReplacements(section) {
78
+ assertRange(section.kind === SectionKind.RowReplacements, "wrong row replacement section kind");
79
+ assertRange(section.bytes.byteLength >= 2, "truncated row replacement header");
80
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
81
+ const count = view.getUint16(0, true);
82
+ assertRange(count === section.itemCount, "row replacement count mismatch");
83
+ const decoder = new TextDecoder("utf-8", { fatal: true });
84
+ const rows = [];
85
+ let offset = 2;
86
+ for (let index = 0; index < count; index += 1) {
87
+ assertRange(offset + 18 <= section.bytes.byteLength, "truncated row replacement");
88
+ const row = view.getUint16(offset, true);
89
+ const revision = view.getBigUint64(offset + 2, true);
90
+ const length = view.getUint32(offset + 10, true);
91
+ const glyphCount = view.getUint16(offset + 14, true);
92
+ const styleCount = view.getUint16(offset + 16, true);
93
+ offset += 18;
94
+ assertRange(offset + length <= section.bytes.byteLength, "row replacement payload out of bounds");
95
+ const text = decoder.decode(section.bytes.subarray(offset, offset + length));
96
+ offset += length;
97
+ const glyphs = [];
98
+ for (let glyphIndex = 0; glyphIndex < glyphCount; glyphIndex += 1) {
99
+ assertRange(offset + 28 <= section.bytes.byteLength, "truncated glyph instance");
100
+ glyphs.push({
101
+ glyphId: view.getUint32(offset, true),
102
+ styleId: view.getUint32(offset + 4, true),
103
+ x: view.getFloat32(offset + 8, true),
104
+ y: view.getFloat32(offset + 12, true),
105
+ width: view.getFloat32(offset + 16, true),
106
+ height: view.getFloat32(offset + 20, true),
107
+ cellStart: view.getUint16(offset + 24, true),
108
+ cellSpan: view.getUint16(offset + 26, true)
109
+ });
110
+ offset += 28;
111
+ }
112
+ const styles = [];
113
+ for (let styleIndex = 0; styleIndex < styleCount; styleIndex += 1) {
114
+ assertRange(offset + 8 <= section.bytes.byteLength, "truncated row style run");
115
+ styles.push({
116
+ styleId: view.getUint32(offset, true),
117
+ cellStart: view.getUint16(offset + 4, true),
118
+ cellSpan: view.getUint16(offset + 6, true)
119
+ });
120
+ offset += 8;
121
+ }
122
+ rows.push({ row, revision, text, glyphs, styles });
123
+ }
124
+ assertRange(offset === section.bytes.byteLength, "trailing row replacement bytes");
125
+ return rows;
126
+ }
127
+ function decodeGlyphDefinitions(section) {
128
+ assertRange(section.kind === SectionKind.GlyphDefinitions, "wrong glyph definition section kind");
129
+ assertRange(section.bytes.byteLength >= 4, "truncated glyph definition header");
130
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
131
+ const count = view.getUint32(0, true);
132
+ assertRange(count === section.itemCount, "glyph definition count mismatch");
133
+ const definitions = [];
134
+ let offset = 4;
135
+ for (let index = 0; index < count; index += 1) {
136
+ assertRange(offset + 20 <= section.bytes.byteLength, "truncated glyph definition");
137
+ const format = view.getUint8(offset + 12);
138
+ assertRange(format === GlyphFormat.Alpha8 || format === GlyphFormat.Rgba8Premultiplied, "invalid glyph format");
139
+ assertRange(view.getUint8(offset + 13) === 0 && view.getUint16(offset + 14, true) === 0, "nonzero glyph reserved bytes");
140
+ const pixelLength = view.getUint32(offset + 16, true);
141
+ const width = view.getUint16(offset + 4, true);
142
+ const height = view.getUint16(offset + 6, true);
143
+ const expectedLength = width * height * (format === GlyphFormat.Alpha8 ? 1 : 4);
144
+ assertRange(pixelLength === expectedLength, "glyph pixel length mismatch");
145
+ offset += 20;
146
+ assertRange(offset + pixelLength <= section.bytes.byteLength, "glyph pixels out of bounds");
147
+ definitions.push({
148
+ id: view.getUint32(offset - 20, true),
149
+ width,
150
+ height,
151
+ bearingX: view.getInt16(offset - 12, true),
152
+ bearingY: view.getInt16(offset - 10, true),
153
+ format,
154
+ pixels: section.bytes.slice(offset, offset + pixelLength)
155
+ });
156
+ offset += pixelLength;
157
+ }
158
+ assertRange(offset === section.bytes.byteLength, "trailing glyph definition bytes");
159
+ return definitions;
160
+ }
161
+ function decodeStyleDefinitions(section) {
162
+ assertRange(section.kind === SectionKind.StyleDefinitions, "wrong style definition section kind");
163
+ assertRange(section.bytes.byteLength >= 4, "truncated style definition header");
164
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
165
+ const count = view.getUint32(0, true);
166
+ assertRange(count === section.itemCount, "style definition count mismatch");
167
+ assertRange(section.bytes.byteLength === 4 + count * 16, "invalid style definition length");
168
+ const styles = [];
169
+ for (let index = 0; index < count; index += 1) {
170
+ const offset = 4 + index * 16;
171
+ const flags = view.getUint16(offset + 4, true);
172
+ const foregroundKind = view.getUint8(offset + 6);
173
+ const backgroundKind = view.getUint8(offset + 7);
174
+ assertRange((flags & ~127) === 0, "invalid style flags");
175
+ assertRange(foregroundKind <= 1 && backgroundKind <= 1, "invalid style color kind");
176
+ assertRange(view.getUint16(offset + 14, true) === 0, "nonzero style reserved bytes");
177
+ styles.push({
178
+ id: view.getUint32(offset, true),
179
+ bold: (flags & 1) !== 0,
180
+ italic: (flags & 2) !== 0,
181
+ faint: (flags & 4) !== 0,
182
+ inverse: (flags & 8) !== 0,
183
+ invisible: (flags & 16) !== 0,
184
+ strikethrough: (flags & 32) !== 0,
185
+ underline: (flags & 64) !== 0,
186
+ ...foregroundKind === 1 ? { foreground: [view.getUint8(offset + 8), view.getUint8(offset + 9), view.getUint8(offset + 10)] } : {},
187
+ ...backgroundKind === 1 ? { background: [view.getUint8(offset + 11), view.getUint8(offset + 12), view.getUint8(offset + 13)] } : {}
188
+ });
189
+ }
190
+ return styles;
191
+ }
192
+ function decodeCursorState(section) {
193
+ assertRange(section.kind === SectionKind.CursorState, "wrong cursor section kind");
194
+ assertRange(section.itemCount === 1, "cursor item count mismatch");
195
+ assertRange(section.bytes.byteLength === 8, "invalid cursor payload length");
196
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
197
+ const visible = view.getUint8(4);
198
+ const style = view.getUint8(5);
199
+ const blinking = view.getUint8(6);
200
+ assertRange(visible <= 1, "invalid cursor visibility");
201
+ assertRange(style <= CursorStyle.HollowBlock, "invalid cursor style");
202
+ assertRange(blinking <= 1, "invalid cursor blinking state");
203
+ assertRange(view.getUint8(7) === 0, "nonzero cursor reserved byte");
204
+ return {
205
+ x: view.getUint16(0, true),
206
+ y: view.getUint16(2, true),
207
+ visible: visible === 1,
208
+ style,
209
+ blinking: blinking === 1
210
+ };
211
+ }
212
+ function decodeScrollbarState(section) {
213
+ assertRange(section.kind === SectionKind.ScrollbarState, "wrong scrollbar section kind");
214
+ assertRange(section.itemCount === 1, "scrollbar item count mismatch");
215
+ assertRange(section.bytes.byteLength === 24, "invalid scrollbar payload size");
216
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
217
+ const total = view.getBigUint64(0, true);
218
+ const offset = view.getBigUint64(8, true);
219
+ const length = view.getBigUint64(16, true);
220
+ assertRange(length <= total, "scrollbar viewport exceeds total rows");
221
+ assertRange(offset <= total - length, "scrollbar offset exceeds scrollable range");
222
+ return { total, offset, length };
223
+ }
224
+ function decodeClipboardWrite(section) {
225
+ assertRange(section.kind === SectionKind.ClipboardWrite, "wrong clipboard section kind");
226
+ assertRange(section.itemCount === 1, "clipboard item count mismatch");
227
+ assertRange(section.bytes.byteLength >= 4, "truncated clipboard payload");
228
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
229
+ const length = view.getUint32(0, true);
230
+ assertRange(length === section.bytes.byteLength - 4, "clipboard length mismatch");
231
+ return new TextDecoder("utf-8", { fatal: true }).decode(section.bytes.subarray(4));
232
+ }
233
+
234
+ // src/performance.ts
235
+ function emptyRenderMetrics() {
236
+ return {
237
+ queueSubmits: 0,
238
+ fullRenders: 0,
239
+ partialRenders: 0,
240
+ damagedRows: 0,
241
+ geometryCacheHits: 0,
242
+ geometryCacheMisses: 0,
243
+ canvasPixels: 0,
244
+ renderPasses: 0,
245
+ drawCalls: 0,
246
+ rectangleVertices: 0,
247
+ monoGlyphVertices: 0,
248
+ colorGlyphVertices: 0,
249
+ fallbackGlyphVertices: 0,
250
+ vertexUploadBytes: 0,
251
+ atlasUploadBytes: 0,
252
+ atlasUploadCalls: 0
253
+ };
254
+ }
255
+
256
+ // src/renderers/types.ts
257
+ function effectiveCursorStyle(view) {
258
+ if (!view.cursor.visible) return null;
259
+ if (!view.focused) return CursorStyle.HollowBlock;
260
+ if (view.cursor.blinking && !view.cursorBlinkVisible) return null;
261
+ return view.cursor.style;
262
+ }
263
+ function physicalPixels(value, dpr) {
264
+ return Math.max(1, Math.round(value * dpr));
265
+ }
266
+ function renderedSizeChanged(current, next) {
267
+ return current.dpr !== next.dpr || physicalPixels(current.width, current.dpr) !== physicalPixels(next.width, next.dpr) || physicalPixels(current.height, current.dpr) !== physicalPixels(next.height, next.dpr);
268
+ }
269
+ var DEFAULT_THEME = {
270
+ background: [40 / 255, 44 / 255, 52 / 255, 1],
271
+ foreground: [1, 1, 1, 1],
272
+ cursor: [1, 1, 1, 1],
273
+ selection: [1, 1, 1, 1],
274
+ selectionForeground: [40 / 255, 44 / 255, 52 / 255, 1]
275
+ };
276
+ var CELL_WIDTH = 7.83;
277
+ var LINE_HEIGHT = 19;
278
+ var ORIGIN_X = 2;
279
+ var ORIGIN_Y = 2;
280
+
281
+ // src/renderers/canvas-renderer.ts
282
+ function css(color) {
283
+ const [red, green, blue, alpha] = color;
284
+ return `rgba(${Math.round(red * 255)}, ${Math.round(green * 255)}, ${Math.round(blue * 255)}, ${alpha})`;
285
+ }
286
+ function ordered(selection) {
287
+ if (!selection) return null;
288
+ const { anchor, focus } = selection;
289
+ return anchor.row < focus.row || anchor.row === focus.row && anchor.column <= focus.column ? [anchor, focus] : [focus, anchor];
290
+ }
291
+ var CanvasTerminalRenderer = class {
292
+ kind = "canvas2d";
293
+ #surfaces = /* @__PURE__ */ new Map();
294
+ #performanceMeasurementEnabled = false;
295
+ setPerformanceMeasurementEnabled(enabled) {
296
+ this.#performanceMeasurementEnabled = enabled;
297
+ }
298
+ mount(id, canvas) {
299
+ const context = canvas.getContext("2d", { alpha: false });
300
+ if (!context) throw new Error("Canvas2D worker backend unavailable");
301
+ this.#surfaces.set(id, { canvas, context, width: 1, height: 1, dpr: 1 });
302
+ }
303
+ unmount(id) {
304
+ this.#surfaces.delete(id);
305
+ }
306
+ resize(id, size) {
307
+ const surface2 = this.#surfaces.get(id);
308
+ if (!surface2) return;
309
+ const changed = renderedSizeChanged(surface2, size);
310
+ Object.assign(surface2, size);
311
+ if (!changed) return;
312
+ surface2.canvas.width = Math.max(1, Math.round(size.width * size.dpr));
313
+ surface2.canvas.height = Math.max(1, Math.round(size.height * size.dpr));
314
+ }
315
+ render(id, view) {
316
+ const surface2 = this.#surfaces.get(id);
317
+ if (!surface2) return;
318
+ const { context, width, height, dpr } = surface2;
319
+ context.save();
320
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
321
+ context.fillStyle = css(view.theme.background);
322
+ context.fillRect(0, 0, width, height);
323
+ const selection = ordered(view.selection);
324
+ if (selection) {
325
+ context.fillStyle = css(view.theme.selection);
326
+ const [start, end] = selection;
327
+ for (let row = start.row; row <= end.row; row += 1) {
328
+ const first = row === start.row ? start.column : 0;
329
+ const last = row === end.row ? end.column : Math.max(0, view.rows[row]?.length ?? 0);
330
+ context.fillRect(
331
+ ORIGIN_X + first * CELL_WIDTH,
332
+ ORIGIN_Y + row * LINE_HEIGHT,
333
+ Math.max(1, last - first + 1) * CELL_WIDTH,
334
+ LINE_HEIGHT
335
+ );
336
+ }
337
+ }
338
+ context.font = "13px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
339
+ context.textBaseline = "top";
340
+ context.fillStyle = css(view.theme.foreground);
341
+ for (let row = 0; row < view.rows.length; row += 1) {
342
+ context.fillText(view.rows[row] ?? "", ORIGIN_X, ORIGIN_Y + row * LINE_HEIGHT);
343
+ }
344
+ if (selection) {
345
+ const [start, end] = selection;
346
+ context.fillStyle = css(view.theme.selectionForeground);
347
+ for (let row = start.row; row <= end.row; row += 1) {
348
+ const first = row === start.row ? start.column : 0;
349
+ const last = row === end.row ? end.column : Math.max(0, view.rows[row]?.length ?? 0);
350
+ context.save();
351
+ context.beginPath();
352
+ context.rect(
353
+ ORIGIN_X + first * CELL_WIDTH,
354
+ ORIGIN_Y + row * LINE_HEIGHT,
355
+ Math.max(1, last - first + 1) * CELL_WIDTH,
356
+ LINE_HEIGHT
357
+ );
358
+ context.clip();
359
+ context.fillText(view.rows[row] ?? "", ORIGIN_X, ORIGIN_Y + row * LINE_HEIGHT);
360
+ context.restore();
361
+ }
362
+ }
363
+ const cursorStyle = effectiveCursorStyle(view);
364
+ if (cursorStyle !== null) {
365
+ const x = ORIGIN_X + view.cursor.x * CELL_WIDTH;
366
+ const y = ORIGIN_Y + view.cursor.y * LINE_HEIGHT;
367
+ const color = cursorStyle === CursorStyle.HollowBlock ? [view.theme.cursor[0], view.theme.cursor[1], view.theme.cursor[2], 1] : view.theme.cursor;
368
+ context.fillStyle = css(color);
369
+ if (cursorStyle === CursorStyle.Bar) {
370
+ context.fillRect(x, y, 2, LINE_HEIGHT);
371
+ } else if (cursorStyle === CursorStyle.Underline) {
372
+ context.fillRect(x, y + LINE_HEIGHT - 2, CELL_WIDTH, 2);
373
+ } else if (cursorStyle === CursorStyle.HollowBlock) {
374
+ context.strokeStyle = css(color);
375
+ context.lineWidth = 1;
376
+ context.strokeRect(x + 0.5, y + 0.5, CELL_WIDTH - 1, LINE_HEIGHT - 1);
377
+ } else {
378
+ context.fillRect(x, y, CELL_WIDTH, LINE_HEIGHT);
379
+ }
380
+ }
381
+ context.restore();
382
+ if (!this.#performanceMeasurementEnabled) return void 0;
383
+ const metrics = emptyRenderMetrics();
384
+ metrics.fullRenders = 1;
385
+ metrics.damagedRows = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
386
+ metrics.canvasPixels = surface2.canvas.width * surface2.canvas.height;
387
+ metrics.renderPasses = 1;
388
+ metrics.drawCalls = 1;
389
+ return metrics;
390
+ }
391
+ };
392
+
393
+ // src/cell-width.ts
394
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
395
+ function splitGraphemes(text) {
396
+ return Array.from(graphemeSegmenter.segment(text), ({ segment }) => segment);
397
+ }
398
+ function graphemeCellWidth(grapheme) {
399
+ const codepoint = grapheme.codePointAt(0) ?? 0;
400
+ return /\p{Extended_Pictographic}/u.test(grapheme) || codepoint >= 4352 && codepoint <= 4447 || codepoint >= 11904 && codepoint <= 42191 || codepoint >= 44032 && codepoint <= 55203 || codepoint >= 63744 && codepoint <= 64255 || codepoint >= 65040 && codepoint <= 65135 || codepoint >= 65280 && codepoint <= 65376 || codepoint >= 127744 && codepoint <= 129791 || codepoint >= 131072 && codepoint <= 262141 ? 2 : 1;
401
+ }
402
+
403
+ // src/renderers/render-damage.ts
404
+ function rowsForDamage(rowCount, damage, sceneValid) {
405
+ const full = !sceneValid || !damage || damage.full;
406
+ if (full) return { full: true, rows: Array.from({ length: rowCount }, (_, row) => row) };
407
+ const rows = /* @__PURE__ */ new Set();
408
+ for (const damagedRow of damage.rows) {
409
+ for (let row = damagedRow - 1; row <= damagedRow + 1; row += 1) {
410
+ if (row >= 0 && row < rowCount) rows.add(row);
411
+ }
412
+ }
413
+ return { full: false, rows: [...rows].sort((left, right) => left - right) };
414
+ }
415
+
416
+ // src/renderers/webgpu-renderer.ts
417
+ var ATLAS_SIZE = 2048;
418
+ var GEOMETRY_CACHE_LIMIT = 8;
419
+ var FONT_STACK = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
420
+ var RECT_SHADER = (
421
+ /* wgsl */
422
+ `
423
+ struct VertexOutput {
424
+ @builtin(position) position: vec4f,
425
+ @location(0) color: vec4f,
426
+ }
427
+
428
+ @vertex fn vertex_main(
429
+ @builtin(vertex_index) vertex_index: u32,
430
+ @location(0) p0: vec2f,
431
+ @location(1) p1: vec2f,
432
+ @location(2) p2: vec2f,
433
+ @location(3) color: vec4f,
434
+ ) -> VertexOutput {
435
+ let p3 = p1 + p2 - p0;
436
+ let positions = array<vec2f, 6>(
437
+ p0,
438
+ p1,
439
+ p2,
440
+ p2,
441
+ p1,
442
+ p3,
443
+ );
444
+ var output: VertexOutput;
445
+ output.position = vec4f(positions[vertex_index], 0.0, 1.0);
446
+ output.color = color;
447
+ return output;
448
+ }
449
+
450
+ @fragment fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
451
+ return vec4f(input.color.rgb * input.color.a, input.color.a);
452
+ }
453
+ `
454
+ );
455
+ function glyphShader(coverageChannel) {
456
+ return (
457
+ /* wgsl */
458
+ `
459
+ @group(0) @binding(0) var atlas: texture_2d<f32>;
460
+ @group(0) @binding(1) var atlas_sampler: sampler;
461
+
462
+ struct VertexOutput {
463
+ @builtin(position) position: vec4f,
464
+ @location(0) uv: vec2f,
465
+ @location(1) color: vec4f,
466
+ }
467
+
468
+ @vertex fn vertex_main(
469
+ @builtin(vertex_index) vertex_index: u32,
470
+ @location(0) bounds: vec4f,
471
+ @location(1) uv_bounds: vec4f,
472
+ @location(2) color: vec4f,
473
+ ) -> VertexOutput {
474
+ let positions = array<vec2f, 6>(
475
+ vec2f(bounds.x, bounds.z),
476
+ vec2f(bounds.y, bounds.z),
477
+ vec2f(bounds.x, bounds.w),
478
+ vec2f(bounds.x, bounds.w),
479
+ vec2f(bounds.y, bounds.z),
480
+ vec2f(bounds.y, bounds.w),
481
+ );
482
+ let uvs = array<vec2f, 6>(
483
+ vec2f(uv_bounds.x, uv_bounds.z),
484
+ vec2f(uv_bounds.y, uv_bounds.z),
485
+ vec2f(uv_bounds.x, uv_bounds.w),
486
+ vec2f(uv_bounds.x, uv_bounds.w),
487
+ vec2f(uv_bounds.y, uv_bounds.z),
488
+ vec2f(uv_bounds.y, uv_bounds.w),
489
+ );
490
+ var output: VertexOutput;
491
+ output.position = vec4f(positions[vertex_index], 0.0, 1.0);
492
+ output.uv = uvs[vertex_index];
493
+ output.color = color;
494
+ return output;
495
+ }
496
+
497
+ @fragment fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
498
+ let coverage = textureSample(atlas, atlas_sampler, input.uv).${coverageChannel};
499
+ let alpha = input.color.a * coverage;
500
+ return vec4f(input.color.rgb * alpha, alpha);
501
+ }
502
+ `
503
+ );
504
+ }
505
+ var MONO_GLYPH_SHADER = glyphShader("r");
506
+ var FALLBACK_GLYPH_SHADER = glyphShader("a");
507
+ var COLOR_GLYPH_SHADER = (
508
+ /* wgsl */
509
+ `
510
+ @group(0) @binding(0) var atlas: texture_2d<f32>;
511
+ @group(0) @binding(1) var atlas_sampler: sampler;
512
+
513
+ struct VertexOutput {
514
+ @builtin(position) position: vec4f,
515
+ @location(0) uv: vec2f,
516
+ @location(1) color: vec4f,
517
+ }
518
+
519
+ @vertex fn vertex_main(
520
+ @builtin(vertex_index) vertex_index: u32,
521
+ @location(0) bounds: vec4f,
522
+ @location(1) uv_bounds: vec4f,
523
+ @location(2) color: vec4f,
524
+ ) -> VertexOutput {
525
+ let positions = array<vec2f, 6>(
526
+ vec2f(bounds.x, bounds.z),
527
+ vec2f(bounds.y, bounds.z),
528
+ vec2f(bounds.x, bounds.w),
529
+ vec2f(bounds.x, bounds.w),
530
+ vec2f(bounds.y, bounds.z),
531
+ vec2f(bounds.y, bounds.w),
532
+ );
533
+ let uvs = array<vec2f, 6>(
534
+ vec2f(uv_bounds.x, uv_bounds.z),
535
+ vec2f(uv_bounds.y, uv_bounds.z),
536
+ vec2f(uv_bounds.x, uv_bounds.w),
537
+ vec2f(uv_bounds.x, uv_bounds.w),
538
+ vec2f(uv_bounds.y, uv_bounds.z),
539
+ vec2f(uv_bounds.y, uv_bounds.w),
540
+ );
541
+ var output: VertexOutput;
542
+ output.position = vec4f(positions[vertex_index], 0.0, 1.0);
543
+ output.uv = uvs[vertex_index];
544
+ output.color = color;
545
+ return output;
546
+ }
547
+
548
+ @fragment fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
549
+ return textureSample(atlas, atlas_sampler, input.uv);
550
+ }
551
+ `
552
+ );
553
+ var BETTER_CRT_SHADER = (
554
+ /* wgsl */
555
+ `
556
+ @group(0) @binding(0) var terminal_image: texture_2d<f32>;
557
+ @group(0) @binding(1) var terminal_sampler: sampler;
558
+
559
+ @vertex fn vertex_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f {
560
+ let positions = array<vec2f, 3>(
561
+ vec2f(-1.0, -1.0),
562
+ vec2f(3.0, -1.0),
563
+ vec2f(-1.0, 3.0),
564
+ );
565
+ return vec4f(positions[vertex_index], 0.0, 1.0);
566
+ }
567
+
568
+ @fragment fn fragment_main(@builtin(position) position: vec4f) -> @location(0) vec4f {
569
+ let dimensions = vec2f(textureDimensions(terminal_image));
570
+ var uv = position.xy / dimensions;
571
+ var dc = abs(vec2f(0.5) - uv);
572
+ dc *= dc;
573
+
574
+ // These constants are the active Ghostty shader's warp = 0.25 and
575
+ // scan = 0.50 values.
576
+ uv.x -= 0.5;
577
+ uv.x *= 1.0 + dc.y * 0.075;
578
+ uv.x += 0.5;
579
+ uv.y -= 0.5;
580
+ uv.y *= 1.0 + dc.x * 0.10;
581
+ uv.y += 0.5;
582
+
583
+ let scanline = abs(sin(position.y) * 0.125);
584
+ let color = textureSample(terminal_image, terminal_sampler, uv).rgb;
585
+ return vec4f(mix(color, vec3f(0.0), scanline), 1.0);
586
+ }
587
+ `
588
+ );
589
+ var premultipliedBlend = {
590
+ color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
591
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
592
+ };
593
+ var DynamicVertexBuffer = class {
594
+ constructor(device, label) {
595
+ this.device = device;
596
+ this.label = label;
597
+ }
598
+ #buffer;
599
+ #capacity = 0;
600
+ write(vertices) {
601
+ if (vertices.byteLength === 0) return void 0;
602
+ if (!this.#buffer || this.#capacity < vertices.byteLength) {
603
+ this.#buffer?.destroy();
604
+ this.#capacity = Math.max(4096, 2 ** Math.ceil(Math.log2(vertices.byteLength)));
605
+ this.#buffer = this.device.createBuffer({
606
+ label: this.label,
607
+ size: this.#capacity,
608
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
609
+ });
610
+ }
611
+ this.device.queue.writeBuffer(this.#buffer, 0, vertices);
612
+ return this.#buffer;
613
+ }
614
+ destroy() {
615
+ this.#buffer?.destroy();
616
+ this.#buffer = void 0;
617
+ }
618
+ };
619
+ var FallbackGlyphAtlas = class {
620
+ constructor(device, bindGroupLayout) {
621
+ this.device = device;
622
+ this.#texture = device.createTexture({
623
+ label: "terminal glyph atlas",
624
+ size: [ATLAS_SIZE, ATLAS_SIZE],
625
+ format: "rgba8unorm",
626
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
627
+ });
628
+ this.#sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
629
+ this.#bindGroup = device.createBindGroup({
630
+ label: "terminal glyph atlas bindings",
631
+ layout: bindGroupLayout,
632
+ entries: [
633
+ { binding: 0, resource: this.#texture.createView() },
634
+ { binding: 1, resource: this.#sampler }
635
+ ]
636
+ });
637
+ }
638
+ #texture;
639
+ #sampler;
640
+ #bindGroup;
641
+ #cache = /* @__PURE__ */ new Map();
642
+ #scratch = new OffscreenCanvas(1, 1);
643
+ #x = 1;
644
+ #y = 1;
645
+ #rowHeight = 0;
646
+ #uploadBytes = 0;
647
+ #uploadCalls = 0;
648
+ #generation = 0;
649
+ get bindGroup() {
650
+ return this.#bindGroup;
651
+ }
652
+ get generation() {
653
+ return this.#generation;
654
+ }
655
+ prepare(values, dpr) {
656
+ const scale = Math.max(1, Math.min(3, dpr));
657
+ const all = [...new Set(values)].filter((value) => value.trim().length > 0);
658
+ const missing = all.filter((value) => !this.#cache.has(`${scale.toFixed(2)}:${value}`));
659
+ if (this.#fits(missing, scale, this.#x, this.#y, this.#rowHeight)) return;
660
+ this.#cache.clear();
661
+ this.#generation += 1;
662
+ this.#x = 1;
663
+ this.#y = 1;
664
+ this.#rowHeight = 0;
665
+ if (!this.#fits(all, scale, this.#x, this.#y, this.#rowHeight)) {
666
+ throw new Error("The diagnostic glyph atlas cannot fit the text visible in one frame");
667
+ }
668
+ }
669
+ #fits(values, scale, startX, startY, startRowHeight) {
670
+ let x = startX;
671
+ let y = startY;
672
+ let rowHeight = startRowHeight;
673
+ for (const value of values) {
674
+ const width = Math.max(1, Math.ceil(CELL_WIDTH * graphemeCellWidth(value) * scale));
675
+ const height = Math.max(1, Math.ceil(LINE_HEIGHT * scale));
676
+ if (x + width + 1 > ATLAS_SIZE) {
677
+ x = 1;
678
+ y += rowHeight + 1;
679
+ rowHeight = 0;
680
+ }
681
+ if (y + height + 1 > ATLAS_SIZE) return false;
682
+ x += width + 1;
683
+ rowHeight = Math.max(rowHeight, height);
684
+ }
685
+ return true;
686
+ }
687
+ glyph(grapheme, dpr) {
688
+ if (grapheme.trim().length === 0) return void 0;
689
+ const scale = Math.max(1, Math.min(3, dpr));
690
+ const cells = graphemeCellWidth(grapheme);
691
+ const key = `${scale.toFixed(2)}:${grapheme}`;
692
+ const cached = this.#cache.get(key);
693
+ if (cached) return cached;
694
+ const width = Math.max(1, Math.ceil(CELL_WIDTH * cells * scale));
695
+ const height = Math.max(1, Math.ceil(LINE_HEIGHT * scale));
696
+ if (this.#x + width + 1 > ATLAS_SIZE) {
697
+ this.#x = 1;
698
+ this.#y += this.#rowHeight + 1;
699
+ this.#rowHeight = 0;
700
+ }
701
+ if (this.#y + height + 1 > ATLAS_SIZE) {
702
+ throw new Error("Temporary glyph atlas exhausted");
703
+ }
704
+ this.#scratch.width = width;
705
+ this.#scratch.height = height;
706
+ const context = this.#scratch.getContext("2d", { willReadFrequently: true });
707
+ if (!context) throw new Error("Glyph raster canvas unavailable");
708
+ context.clearRect(0, 0, width, height);
709
+ context.fillStyle = "white";
710
+ context.font = `${13 * scale}px ${FONT_STACK}`;
711
+ context.textBaseline = "top";
712
+ context.fillText(grapheme, 0, Math.max(0, Math.floor((height - 15 * scale) / 2)));
713
+ const image = context.getImageData(0, 0, width, height);
714
+ this.device.queue.writeTexture(
715
+ { texture: this.#texture, origin: [this.#x, this.#y] },
716
+ image.data,
717
+ { bytesPerRow: width * 4, rowsPerImage: height },
718
+ [width, height]
719
+ );
720
+ this.#uploadBytes += image.data.byteLength;
721
+ this.#uploadCalls += 1;
722
+ const location = {
723
+ u0: this.#x / ATLAS_SIZE,
724
+ v0: this.#y / ATLAS_SIZE,
725
+ u1: (this.#x + width) / ATLAS_SIZE,
726
+ v1: (this.#y + height) / ATLAS_SIZE,
727
+ cells
728
+ };
729
+ this.#cache.set(key, location);
730
+ this.#x += width + 1;
731
+ this.#rowHeight = Math.max(this.#rowHeight, height);
732
+ return location;
733
+ }
734
+ destroy() {
735
+ this.#texture.destroy();
736
+ }
737
+ uploadMetrics() {
738
+ return { bytes: this.#uploadBytes, calls: this.#uploadCalls };
739
+ }
740
+ };
741
+ var NativeGlyphAtlas = class {
742
+ constructor(device, bindGroupLayout, label, format) {
743
+ this.device = device;
744
+ this.label = label;
745
+ this.format = format;
746
+ this.#texture = device.createTexture({
747
+ label,
748
+ size: [ATLAS_SIZE, ATLAS_SIZE],
749
+ format,
750
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
751
+ });
752
+ this.#sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" });
753
+ this.#bindGroup = device.createBindGroup({
754
+ label: `${label} bindings`,
755
+ layout: bindGroupLayout,
756
+ entries: [
757
+ { binding: 0, resource: this.#texture.createView() },
758
+ { binding: 1, resource: this.#sampler }
759
+ ]
760
+ });
761
+ }
762
+ #texture;
763
+ #sampler;
764
+ #bindGroup;
765
+ #cache = /* @__PURE__ */ new Map();
766
+ #x = 1;
767
+ #y = 1;
768
+ #rowHeight = 0;
769
+ #uploadBytes = 0;
770
+ #uploadCalls = 0;
771
+ #generation = 0;
772
+ get bindGroup() {
773
+ return this.#bindGroup;
774
+ }
775
+ get generation() {
776
+ return this.#generation;
777
+ }
778
+ prepare(definitions) {
779
+ const all = /* @__PURE__ */ new Map();
780
+ const missing = /* @__PURE__ */ new Map();
781
+ for (const definition of definitions) {
782
+ all.set(definition.id, definition);
783
+ if (!this.#cache.has(definition.id)) missing.set(definition.id, definition);
784
+ }
785
+ if (this.#fits(missing.values(), this.#x, this.#y, this.#rowHeight)) return;
786
+ this.#cache.clear();
787
+ this.#generation += 1;
788
+ this.#x = 1;
789
+ this.#y = 1;
790
+ this.#rowHeight = 0;
791
+ if (!this.#fits(all.values(), this.#x, this.#y, this.#rowHeight)) {
792
+ throw new Error(`${this.label} cannot fit the glyphs visible in one frame`);
793
+ }
794
+ }
795
+ #fits(definitions, startX, startY, startRowHeight) {
796
+ let x = startX;
797
+ let y = startY;
798
+ let rowHeight = startRowHeight;
799
+ for (const { width, height } of definitions) {
800
+ if (width + 2 > ATLAS_SIZE || height + 2 > ATLAS_SIZE) return false;
801
+ if (x + width + 1 > ATLAS_SIZE) {
802
+ x = 1;
803
+ y += rowHeight + 1;
804
+ rowHeight = 0;
805
+ }
806
+ if (y + height + 1 > ATLAS_SIZE) return false;
807
+ x += width + 1;
808
+ rowHeight = Math.max(rowHeight, height);
809
+ }
810
+ return true;
811
+ }
812
+ glyph(definition) {
813
+ const cached = this.#cache.get(definition.id);
814
+ if (cached) return cached;
815
+ const { width, height } = definition;
816
+ if (this.#x + width + 1 > ATLAS_SIZE) {
817
+ this.#x = 1;
818
+ this.#y += this.#rowHeight + 1;
819
+ this.#rowHeight = 0;
820
+ }
821
+ if (this.#y + height + 1 > ATLAS_SIZE) throw new Error(`${this.label} exhausted`);
822
+ const pixels = definition.pixels;
823
+ const bytesPerPixel = this.format === "r8unorm" ? 1 : 4;
824
+ this.device.queue.writeTexture(
825
+ { texture: this.#texture, origin: [this.#x, this.#y] },
826
+ pixels,
827
+ { bytesPerRow: width * bytesPerPixel, rowsPerImage: height },
828
+ [width, height]
829
+ );
830
+ this.#uploadBytes += pixels.byteLength;
831
+ this.#uploadCalls += 1;
832
+ const location = {
833
+ u0: this.#x / ATLAS_SIZE,
834
+ v0: this.#y / ATLAS_SIZE,
835
+ u1: (this.#x + width) / ATLAS_SIZE,
836
+ v1: (this.#y + height) / ATLAS_SIZE,
837
+ cells: 0
838
+ };
839
+ this.#cache.set(definition.id, location);
840
+ this.#x += width + 1;
841
+ this.#rowHeight = Math.max(this.#rowHeight, height);
842
+ return location;
843
+ }
844
+ destroy() {
845
+ this.#texture.destroy();
846
+ }
847
+ uploadMetrics() {
848
+ return { bytes: this.#uploadBytes, calls: this.#uploadCalls };
849
+ }
850
+ };
851
+ function destroyCachedGeometry(geometry) {
852
+ geometry.rectangleBuffer?.destroy();
853
+ geometry.glyphBuffer?.destroy();
854
+ geometry.colorGlyphBuffer?.destroy();
855
+ geometry.fallbackGlyphBuffer?.destroy();
856
+ }
857
+ function clearGeometryCache(surface2) {
858
+ for (const geometry of surface2.geometryCache.values()) destroyCachedGeometry(geometry);
859
+ surface2.geometryCache.clear();
860
+ surface2.geometryCandidates.clear();
861
+ }
862
+ function createCachedVertexBuffer(device, label, data) {
863
+ if (data.byteLength === 0) return void 0;
864
+ const buffer = device.createBuffer({
865
+ label,
866
+ size: data.byteLength,
867
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
868
+ });
869
+ device.queue.writeBuffer(buffer, 0, data);
870
+ return buffer;
871
+ }
872
+ function geometryCacheKey(view, rows, hasNativeRows, atlasGenerations) {
873
+ const selection = view.selection ? `${view.selection.anchor.row},${view.selection.anchor.column},${view.selection.focus.row},${view.selection.focus.column}` : "-";
874
+ return [
875
+ view.sessionEpoch.toString(),
876
+ view.layoutEpoch.toString(),
877
+ hasNativeRows ? "native" : "fallback",
878
+ rows.map((row) => `${row}:${view.rowRevisions[row]?.toString() ?? "-"}`).join(","),
879
+ [
880
+ ...view.theme.background,
881
+ ...view.theme.foreground,
882
+ ...view.theme.selection,
883
+ ...view.theme.selectionForeground
884
+ ].join(","),
885
+ selection,
886
+ atlasGenerations.join(",")
887
+ ].join("|");
888
+ }
889
+ function pushCursorVertices(vertices, view, renderRowSet, scale, viewportWidth, viewportHeight) {
890
+ const cursorStyle = effectiveCursorStyle(view);
891
+ if (cursorStyle === null || !renderRowSet.has(view.cursor.y)) return;
892
+ const x = (ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale;
893
+ const y = (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale;
894
+ const width = CELL_WIDTH * scale;
895
+ const height = LINE_HEIGHT * scale;
896
+ const stroke = Math.max(1, Math.round(scale));
897
+ const cursorColor = cursorStyle === CursorStyle.HollowBlock ? [view.theme.cursor[0], view.theme.cursor[1], view.theme.cursor[2], 1] : view.theme.cursor;
898
+ if (cursorStyle === CursorStyle.Bar) {
899
+ pushRectangle(
900
+ vertices,
901
+ x,
902
+ y,
903
+ Math.max(2, Math.round(2 * scale)),
904
+ height,
905
+ cursorColor,
906
+ viewportWidth,
907
+ viewportHeight
908
+ );
909
+ } else if (cursorStyle === CursorStyle.Underline) {
910
+ const thickness = Math.max(2, Math.round(2 * scale));
911
+ pushRectangle(vertices, x, y + height - thickness, width, thickness, cursorColor, viewportWidth, viewportHeight);
912
+ } else if (cursorStyle === CursorStyle.HollowBlock) {
913
+ pushRectangle(vertices, x, y, width, stroke, cursorColor, viewportWidth, viewportHeight);
914
+ pushRectangle(vertices, x, y + height - stroke, width, stroke, cursorColor, viewportWidth, viewportHeight);
915
+ pushRectangle(
916
+ vertices,
917
+ x,
918
+ y + stroke,
919
+ stroke,
920
+ Math.max(0, height - stroke * 2),
921
+ cursorColor,
922
+ viewportWidth,
923
+ viewportHeight
924
+ );
925
+ pushRectangle(
926
+ vertices,
927
+ x + width - stroke,
928
+ y + stroke,
929
+ stroke,
930
+ Math.max(0, height - stroke * 2),
931
+ cursorColor,
932
+ viewportWidth,
933
+ viewportHeight
934
+ );
935
+ } else {
936
+ pushRectangle(vertices, x, y, width, height, cursorColor, viewportWidth, viewportHeight);
937
+ }
938
+ }
939
+ function clipX(pixel, width) {
940
+ return pixel / Math.max(1, width) * 2 - 1;
941
+ }
942
+ function clipY(pixel, height) {
943
+ return 1 - pixel / Math.max(1, height) * 2;
944
+ }
945
+ function pushRectangle(output, x, y, width, height, color, viewportWidth, viewportHeight) {
946
+ const left = clipX(x, viewportWidth);
947
+ const right = clipX(x + width, viewportWidth);
948
+ const top = clipY(y, viewportHeight);
949
+ const bottom = clipY(y + height, viewportHeight);
950
+ output.push(left, top, right, top, left, bottom, ...color);
951
+ }
952
+ function pushGlyph(output, x, y, width, height, glyph, color, viewportWidth, viewportHeight) {
953
+ const left = clipX(x, viewportWidth);
954
+ const right = clipX(x + width, viewportWidth);
955
+ const top = clipY(y, viewportHeight);
956
+ const bottom = clipY(y + height, viewportHeight);
957
+ output.push(left, right, top, bottom, glyph.u0, glyph.u1, glyph.v0, glyph.v1, ...color);
958
+ }
959
+ function ordered2(selection) {
960
+ if (!selection) return null;
961
+ const { anchor, focus } = selection;
962
+ return anchor.row < focus.row || anchor.row === focus.row && anchor.column <= focus.column ? [anchor, focus] : [focus, anchor];
963
+ }
964
+ function cellLength(text) {
965
+ return splitGraphemes(text).reduce((total, grapheme) => total + graphemeCellWidth(grapheme), 0);
966
+ }
967
+ var BOX_DIRECTIONS = /* @__PURE__ */ new Map([
968
+ ["\u2500", "lr"],
969
+ ["\u2501", "lr"],
970
+ ["\u2502", "ud"],
971
+ ["\u2503", "ud"],
972
+ ["\u250C", "rd"],
973
+ ["\u250D", "rd"],
974
+ ["\u250E", "rd"],
975
+ ["\u250F", "rd"],
976
+ ["\u2510", "ld"],
977
+ ["\u2511", "ld"],
978
+ ["\u2512", "ld"],
979
+ ["\u2513", "ld"],
980
+ ["\u2514", "ru"],
981
+ ["\u2515", "ru"],
982
+ ["\u2516", "ru"],
983
+ ["\u2517", "ru"],
984
+ ["\u2518", "lu"],
985
+ ["\u2519", "lu"],
986
+ ["\u251A", "lu"],
987
+ ["\u251B", "lu"],
988
+ ["\u251C", "udr"],
989
+ ["\u251D", "udr"],
990
+ ["\u251E", "udr"],
991
+ ["\u2523", "udr"],
992
+ ["\u2524", "udl"],
993
+ ["\u2525", "udl"],
994
+ ["\u2526", "udl"],
995
+ ["\u252B", "udl"],
996
+ ["\u252C", "lrd"],
997
+ ["\u252F", "lrd"],
998
+ ["\u2530", "lrd"],
999
+ ["\u2533", "lrd"],
1000
+ ["\u2534", "lru"],
1001
+ ["\u2537", "lru"],
1002
+ ["\u2538", "lru"],
1003
+ ["\u253B", "lru"],
1004
+ ["\u253C", "lrud"],
1005
+ ["\u254B", "lrud"],
1006
+ ["\u2574", "l"],
1007
+ ["\u2576", "r"],
1008
+ ["\u2575", "u"],
1009
+ ["\u2577", "d"],
1010
+ ["\u2550", "lr"],
1011
+ ["\u2551", "ud"],
1012
+ ["\u2554", "rd"],
1013
+ ["\u2557", "ld"],
1014
+ ["\u255A", "ru"],
1015
+ ["\u255D", "lu"],
1016
+ ["\u2560", "udr"],
1017
+ ["\u2563", "udl"],
1018
+ ["\u2566", "lrd"],
1019
+ ["\u2569", "lru"],
1020
+ ["\u256C", "lrud"],
1021
+ ["\u256D", "rd"],
1022
+ ["\u256E", "ld"],
1023
+ ["\u256F", "lu"],
1024
+ ["\u2570", "ru"]
1025
+ ]);
1026
+ var ROUNDED_BOX_CORNERS = /* @__PURE__ */ new Set(["\u256D", "\u256E", "\u256F", "\u2570"]);
1027
+ function specialDrawingCells(text) {
1028
+ const box = /* @__PURE__ */ new Map();
1029
+ const block = /* @__PURE__ */ new Map();
1030
+ let column = 0;
1031
+ let simple = true;
1032
+ for (let index = 0; index < text.length; index += 1) {
1033
+ const codepoint = text.charCodeAt(index);
1034
+ if (codepoint <= 127) {
1035
+ column += 1;
1036
+ } else if (codepoint >= 9600 && codepoint <= 9631) {
1037
+ block.set(column, text[index]);
1038
+ column += 1;
1039
+ } else if (BOX_DIRECTIONS.has(text[index])) {
1040
+ box.set(column, text[index]);
1041
+ column += 1;
1042
+ } else {
1043
+ simple = false;
1044
+ break;
1045
+ }
1046
+ }
1047
+ if (simple) return { box, block };
1048
+ box.clear();
1049
+ block.clear();
1050
+ column = 0;
1051
+ for (const grapheme of splitGraphemes(text)) {
1052
+ const codepoint = grapheme.codePointAt(0) ?? 0;
1053
+ if (codepoint >= 9600 && codepoint <= 9631) block.set(column, grapheme);
1054
+ else if (BOX_DIRECTIONS.has(grapheme)) box.set(column, grapheme);
1055
+ column += graphemeCellWidth(grapheme);
1056
+ }
1057
+ return { box, block };
1058
+ }
1059
+ function rgb(color) {
1060
+ return [color[0] / 255, color[1] / 255, color[2] / 255, 1];
1061
+ }
1062
+ function over(source, backdrop) {
1063
+ if (source[3] >= 1) return source;
1064
+ if (source[3] <= 0) return backdrop;
1065
+ const alpha = source[3] + backdrop[3] * (1 - source[3]);
1066
+ if (alpha <= Number.EPSILON) return [0, 0, 0, 0];
1067
+ return [
1068
+ (source[0] * source[3] + backdrop[0] * backdrop[3] * (1 - source[3])) / alpha,
1069
+ (source[1] * source[3] + backdrop[1] * backdrop[3] * (1 - source[3])) / alpha,
1070
+ (source[2] * source[3] + backdrop[2] * backdrop[3] * (1 - source[3])) / alpha,
1071
+ alpha
1072
+ ];
1073
+ }
1074
+ function selectionContains(selection, row, column) {
1075
+ if (!selection) return false;
1076
+ const [start, end] = selection;
1077
+ if (row < start.row || row > end.row) return false;
1078
+ const first = row === start.row ? start.column : 0;
1079
+ const last = row === end.row ? end.column : Number.MAX_SAFE_INTEGER;
1080
+ return column >= first && column <= last;
1081
+ }
1082
+ function resolveStyle(style, theme) {
1083
+ let foreground = style?.foreground ? rgb(style.foreground) : theme.foreground;
1084
+ let background = style?.background ? rgb(style.background) : null;
1085
+ if (style?.inverse) {
1086
+ const originalForeground = foreground;
1087
+ foreground = background ?? theme.background;
1088
+ background = originalForeground;
1089
+ }
1090
+ if (style?.faint) foreground = [foreground[0], foreground[1], foreground[2], foreground[3] * 0.55];
1091
+ return {
1092
+ foreground,
1093
+ background,
1094
+ underline: style?.underline ?? false,
1095
+ strikethrough: style?.strikethrough ?? false,
1096
+ invisible: style?.invisible ?? false
1097
+ };
1098
+ }
1099
+ function pushBoxDrawing(output, grapheme, column, row, scale, color, viewportWidth, viewportHeight) {
1100
+ const directions = BOX_DIRECTIONS.get(grapheme);
1101
+ if (!directions) return;
1102
+ const left = (ORIGIN_X + column * CELL_WIDTH) * scale;
1103
+ const right = (ORIGIN_X + (column + 1) * CELL_WIDTH) * scale;
1104
+ const top = (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1105
+ const bottom = (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale;
1106
+ const stroke = Math.max(1, Math.round(scale));
1107
+ const centerX = Math.round((left + right - stroke) / 2);
1108
+ const centerY = Math.round((top + bottom - stroke) / 2);
1109
+ if (ROUNDED_BOX_CORNERS.has(grapheme)) {
1110
+ const x = centerX + stroke / 2;
1111
+ const y = centerY + stroke / 2;
1112
+ const points = [];
1113
+ const [originX, originY, startAngle, endAngle] = grapheme === "\u256D" ? [right, bottom, -Math.PI / 2, -Math.PI] : grapheme === "\u256E" ? [left, bottom, -Math.PI / 2, 0] : grapheme === "\u256F" ? [left, top, Math.PI / 2, 0] : [right, top, Math.PI, Math.PI / 2];
1114
+ const radiusX = Math.abs(right - x);
1115
+ const radiusY = Math.abs(bottom - y);
1116
+ const steps = Math.max(5, Math.ceil(Math.max(radiusX, radiusY) / 2));
1117
+ for (let step = 0; step <= steps; step += 1) {
1118
+ const angle = startAngle + (endAngle - startAngle) * (step / steps);
1119
+ points.push([originX + Math.cos(angle) * radiusX, originY + Math.sin(angle) * radiusY]);
1120
+ }
1121
+ for (let index = 1; index < points.length; index += 1) {
1122
+ const from = points[index - 1];
1123
+ const to = points[index];
1124
+ const dx = to[0] - from[0];
1125
+ const dy = to[1] - from[1];
1126
+ const length = Math.hypot(dx, dy);
1127
+ if (length <= Number.EPSILON) continue;
1128
+ const nx = -dy / length * stroke / 2;
1129
+ const ny = dx / length * stroke / 2;
1130
+ output.push(
1131
+ clipX(from[0] + nx, viewportWidth),
1132
+ clipY(from[1] + ny, viewportHeight),
1133
+ clipX(to[0] + nx, viewportWidth),
1134
+ clipY(to[1] + ny, viewportHeight),
1135
+ clipX(from[0] - nx, viewportWidth),
1136
+ clipY(from[1] - ny, viewportHeight),
1137
+ ...color
1138
+ );
1139
+ }
1140
+ return;
1141
+ }
1142
+ if (directions.includes("u"))
1143
+ pushRectangle(output, centerX, top, stroke, centerY + stroke - top, color, viewportWidth, viewportHeight);
1144
+ if (directions.includes("d"))
1145
+ pushRectangle(output, centerX, centerY, stroke, bottom - centerY, color, viewportWidth, viewportHeight);
1146
+ if (directions.includes("l"))
1147
+ pushRectangle(output, left, centerY, centerX + stroke - left, stroke, color, viewportWidth, viewportHeight);
1148
+ if (directions.includes("r"))
1149
+ pushRectangle(output, centerX, centerY, right - centerX, stroke, color, viewportWidth, viewportHeight);
1150
+ }
1151
+ var LOWER_BLOCK_RECTANGLES = Array.from({ length: 7 }, (_, index) => [
1152
+ [0, 1 - (index + 1) / 8, 1, 1]
1153
+ ]);
1154
+ var LEFT_BLOCK_RECTANGLES = Array.from({ length: 7 }, (_, index) => [
1155
+ [0, 0, (7 - index) / 8, 1]
1156
+ ]);
1157
+ var FULL_BLOCK_RECTANGLES = [[0, 0, 1, 1]];
1158
+ var RIGHT_HALF_RECTANGLES = [[0.5, 0, 1, 1]];
1159
+ var UPPER_EIGHTH_RECTANGLES = [[0, 0, 1, 1 / 8]];
1160
+ var RIGHT_EIGHTH_RECTANGLES = [[7 / 8, 0, 1, 1]];
1161
+ var QUADRANT_RECTANGLES = [
1162
+ [0, 0, 0.5, 0.5],
1163
+ [0.5, 0, 1, 0.5],
1164
+ [0, 0.5, 0.5, 1],
1165
+ [0.5, 0.5, 1, 1]
1166
+ ];
1167
+ var QUADRANT_BLOCK_RECTANGLES = new Map(
1168
+ [
1169
+ [9622, [2]],
1170
+ [9623, [3]],
1171
+ [9624, [0]],
1172
+ [9625, [0, 2, 3]],
1173
+ [9626, [0, 3]],
1174
+ [9627, [0, 1, 2]],
1175
+ [9628, [0, 1, 3]],
1176
+ [9629, [1]],
1177
+ [9630, [1, 2]],
1178
+ [9631, [1, 2, 3]]
1179
+ ].map(([codepoint, quadrants]) => [
1180
+ codepoint,
1181
+ quadrants.map((quadrant) => QUADRANT_RECTANGLES[quadrant])
1182
+ ])
1183
+ );
1184
+ function blockElementRectangles(codepoint) {
1185
+ if (codepoint >= 9601 && codepoint <= 9607) return LOWER_BLOCK_RECTANGLES[codepoint - 9601];
1186
+ if (codepoint === 9608) return FULL_BLOCK_RECTANGLES;
1187
+ if (codepoint >= 9609 && codepoint <= 9615) return LEFT_BLOCK_RECTANGLES[codepoint - 9609];
1188
+ if (codepoint === 9616) return RIGHT_HALF_RECTANGLES;
1189
+ if (codepoint === 9620) return UPPER_EIGHTH_RECTANGLES;
1190
+ if (codepoint === 9621) return RIGHT_EIGHTH_RECTANGLES;
1191
+ return QUADRANT_BLOCK_RECTANGLES.get(codepoint);
1192
+ }
1193
+ function pushBlockElement(output, grapheme, column, row, scale, color, viewportWidth, viewportHeight) {
1194
+ const codepoint = grapheme.charCodeAt(0);
1195
+ if (codepoint < 9600 || codepoint > 9631) return false;
1196
+ const cellLeft = (ORIGIN_X + column * CELL_WIDTH) * scale;
1197
+ const cellRight = (ORIGIN_X + (column + 1) * CELL_WIDTH) * scale;
1198
+ const cellTop = (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1199
+ const cellBottom = (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale;
1200
+ if (codepoint === 9600) {
1201
+ const x0 = Math.round(cellLeft);
1202
+ const x1 = Math.round(cellRight);
1203
+ const y0 = Math.round(cellTop);
1204
+ const y1 = Math.round(cellTop + (cellBottom - cellTop) * 0.5);
1205
+ pushRectangle(output, x0, y0, x1 - x0, y1 - y0, color, viewportWidth, viewportHeight);
1206
+ return true;
1207
+ }
1208
+ const rectangles = blockElementRectangles(codepoint);
1209
+ if (!rectangles) return false;
1210
+ for (const [left, top, right, bottom] of rectangles) {
1211
+ const x0 = Math.round(cellLeft + (cellRight - cellLeft) * left);
1212
+ const x1 = Math.round(cellLeft + (cellRight - cellLeft) * right);
1213
+ const y0 = Math.round(cellTop + (cellBottom - cellTop) * top);
1214
+ const y1 = Math.round(cellTop + (cellBottom - cellTop) * bottom);
1215
+ pushRectangle(output, x0, y0, x1 - x0, y1 - y0, color, viewportWidth, viewportHeight);
1216
+ }
1217
+ return true;
1218
+ }
1219
+ var WebGpuTerminalRenderer = class _WebGpuTerminalRenderer {
1220
+ constructor(device, format) {
1221
+ this.device = device;
1222
+ this.format = format;
1223
+ const rectangleModule = device.createShaderModule({ label: "terminal rectangle shader", code: RECT_SHADER });
1224
+ const glyphModule = device.createShaderModule({ label: "terminal glyph shader", code: MONO_GLYPH_SHADER });
1225
+ const fallbackGlyphModule = device.createShaderModule({
1226
+ label: "terminal fallback glyph shader",
1227
+ code: FALLBACK_GLYPH_SHADER
1228
+ });
1229
+ const colorGlyphModule = device.createShaderModule({
1230
+ label: "terminal color glyph shader",
1231
+ code: COLOR_GLYPH_SHADER
1232
+ });
1233
+ const postProcessModule = device.createShaderModule({ label: "Ghostty bettercrt shader", code: BETTER_CRT_SHADER });
1234
+ this.#rectanglePipeline = device.createRenderPipeline({
1235
+ label: "terminal rectangle pipeline",
1236
+ layout: "auto",
1237
+ vertex: {
1238
+ module: rectangleModule,
1239
+ entryPoint: "vertex_main",
1240
+ buffers: [
1241
+ {
1242
+ arrayStride: 40,
1243
+ stepMode: "instance",
1244
+ attributes: [
1245
+ { shaderLocation: 0, offset: 0, format: "float32x2" },
1246
+ { shaderLocation: 1, offset: 8, format: "float32x2" },
1247
+ { shaderLocation: 2, offset: 16, format: "float32x2" },
1248
+ { shaderLocation: 3, offset: 24, format: "float32x4" }
1249
+ ]
1250
+ }
1251
+ ]
1252
+ },
1253
+ fragment: {
1254
+ module: rectangleModule,
1255
+ entryPoint: "fragment_main",
1256
+ targets: [{ format, blend: premultipliedBlend }]
1257
+ },
1258
+ primitive: { topology: "triangle-list" }
1259
+ });
1260
+ this.#glyphPipeline = device.createRenderPipeline({
1261
+ label: "terminal glyph pipeline",
1262
+ layout: "auto",
1263
+ vertex: {
1264
+ module: glyphModule,
1265
+ entryPoint: "vertex_main",
1266
+ buffers: [
1267
+ {
1268
+ arrayStride: 48,
1269
+ stepMode: "instance",
1270
+ attributes: [
1271
+ { shaderLocation: 0, offset: 0, format: "float32x4" },
1272
+ { shaderLocation: 1, offset: 16, format: "float32x4" },
1273
+ { shaderLocation: 2, offset: 32, format: "float32x4" }
1274
+ ]
1275
+ }
1276
+ ]
1277
+ },
1278
+ fragment: { module: glyphModule, entryPoint: "fragment_main", targets: [{ format, blend: premultipliedBlend }] },
1279
+ primitive: { topology: "triangle-list" }
1280
+ });
1281
+ this.#fallbackGlyphPipeline = device.createRenderPipeline({
1282
+ label: "terminal fallback glyph pipeline",
1283
+ layout: "auto",
1284
+ vertex: {
1285
+ module: fallbackGlyphModule,
1286
+ entryPoint: "vertex_main",
1287
+ buffers: [
1288
+ {
1289
+ arrayStride: 48,
1290
+ stepMode: "instance",
1291
+ attributes: [
1292
+ { shaderLocation: 0, offset: 0, format: "float32x4" },
1293
+ { shaderLocation: 1, offset: 16, format: "float32x4" },
1294
+ { shaderLocation: 2, offset: 32, format: "float32x4" }
1295
+ ]
1296
+ }
1297
+ ]
1298
+ },
1299
+ fragment: {
1300
+ module: fallbackGlyphModule,
1301
+ entryPoint: "fragment_main",
1302
+ targets: [{ format, blend: premultipliedBlend }]
1303
+ },
1304
+ primitive: { topology: "triangle-list" }
1305
+ });
1306
+ this.#colorGlyphPipeline = device.createRenderPipeline({
1307
+ label: "terminal color glyph pipeline",
1308
+ layout: "auto",
1309
+ vertex: {
1310
+ module: colorGlyphModule,
1311
+ entryPoint: "vertex_main",
1312
+ buffers: [
1313
+ {
1314
+ arrayStride: 48,
1315
+ stepMode: "instance",
1316
+ attributes: [
1317
+ { shaderLocation: 0, offset: 0, format: "float32x4" },
1318
+ { shaderLocation: 1, offset: 16, format: "float32x4" },
1319
+ { shaderLocation: 2, offset: 32, format: "float32x4" }
1320
+ ]
1321
+ }
1322
+ ]
1323
+ },
1324
+ fragment: {
1325
+ module: colorGlyphModule,
1326
+ entryPoint: "fragment_main",
1327
+ targets: [{ format, blend: premultipliedBlend }]
1328
+ },
1329
+ primitive: { topology: "triangle-list" }
1330
+ });
1331
+ this.#postProcessPipeline = device.createRenderPipeline({
1332
+ label: "Ghostty custom shader pipeline",
1333
+ layout: "auto",
1334
+ vertex: { module: postProcessModule, entryPoint: "vertex_main" },
1335
+ fragment: { module: postProcessModule, entryPoint: "fragment_main", targets: [{ format }] },
1336
+ primitive: { topology: "triangle-list" }
1337
+ });
1338
+ this.#postProcessSampler = device.createSampler({
1339
+ label: "Ghostty custom shader sampler",
1340
+ addressModeU: "clamp-to-edge",
1341
+ addressModeV: "clamp-to-edge",
1342
+ minFilter: "linear",
1343
+ magFilter: "linear"
1344
+ });
1345
+ this.#monoAtlas = new NativeGlyphAtlas(
1346
+ device,
1347
+ this.#glyphPipeline.getBindGroupLayout(0),
1348
+ "native monochrome glyph atlas",
1349
+ "r8unorm"
1350
+ );
1351
+ this.#colorAtlas = new NativeGlyphAtlas(
1352
+ device,
1353
+ this.#colorGlyphPipeline.getBindGroupLayout(0),
1354
+ "native color glyph atlas",
1355
+ "rgba8unorm"
1356
+ );
1357
+ this.#fallbackAtlas = new FallbackGlyphAtlas(device, this.#fallbackGlyphPipeline.getBindGroupLayout(0));
1358
+ }
1359
+ kind = "webgpu";
1360
+ #surfaces = /* @__PURE__ */ new Map();
1361
+ #rectanglePipeline;
1362
+ #glyphPipeline;
1363
+ #fallbackGlyphPipeline;
1364
+ #colorGlyphPipeline;
1365
+ #postProcessPipeline;
1366
+ #postProcessSampler;
1367
+ #monoAtlas;
1368
+ #colorAtlas;
1369
+ #fallbackAtlas;
1370
+ #performanceMeasurementEnabled = false;
1371
+ static async create(onLost) {
1372
+ if (!navigator.gpu) throw new Error("WebGPU is unavailable");
1373
+ const adapter = await navigator.gpu.requestAdapter();
1374
+ if (!adapter) throw new Error("No WebGPU adapter is available");
1375
+ const device = await adapter.requestDevice({ label: "Electron Ghostty terminal renderer" });
1376
+ device.addEventListener("uncapturederror", (event) => {
1377
+ console.error("[terminal-renderer] WebGPU validation error", event.error);
1378
+ });
1379
+ void device.lost.then((info) => {
1380
+ if (info.reason !== "destroyed") onLost(info);
1381
+ });
1382
+ return new _WebGpuTerminalRenderer(device, navigator.gpu.getPreferredCanvasFormat());
1383
+ }
1384
+ setPerformanceMeasurementEnabled(enabled) {
1385
+ this.#performanceMeasurementEnabled = enabled;
1386
+ }
1387
+ mount(id, canvas) {
1388
+ const context = canvas.getContext("webgpu");
1389
+ if (!context) throw new Error("WebGPU canvas context unavailable");
1390
+ const sceneTexture = this.#createSceneTexture(canvas.width, canvas.height);
1391
+ const surface2 = {
1392
+ canvas,
1393
+ context,
1394
+ sceneTexture,
1395
+ postProcessBindGroup: this.#createPostProcessBindGroup(sceneTexture),
1396
+ width: 1,
1397
+ height: 1,
1398
+ dpr: 1,
1399
+ rectangleBuffer: new DynamicVertexBuffer(this.device, `rectangles ${id}`),
1400
+ glyphBuffer: new DynamicVertexBuffer(this.device, `glyphs ${id}`),
1401
+ colorGlyphBuffer: new DynamicVertexBuffer(this.device, `color glyphs ${id}`),
1402
+ fallbackGlyphBuffer: new DynamicVertexBuffer(this.device, `fallback glyphs ${id}`),
1403
+ cursorBuffer: new DynamicVertexBuffer(this.device, `cursor ${id}`),
1404
+ geometryCache: /* @__PURE__ */ new Map(),
1405
+ geometryCandidates: /* @__PURE__ */ new Map(),
1406
+ sceneValid: false
1407
+ };
1408
+ this.#surfaces.set(id, surface2);
1409
+ this.#configure(surface2);
1410
+ }
1411
+ unmount(id) {
1412
+ const surface2 = this.#surfaces.get(id);
1413
+ if (!surface2) return;
1414
+ surface2.context.unconfigure();
1415
+ surface2.sceneTexture.destroy();
1416
+ surface2.rectangleBuffer.destroy();
1417
+ surface2.glyphBuffer.destroy();
1418
+ surface2.colorGlyphBuffer.destroy();
1419
+ surface2.fallbackGlyphBuffer.destroy();
1420
+ surface2.cursorBuffer.destroy();
1421
+ clearGeometryCache(surface2);
1422
+ this.#surfaces.delete(id);
1423
+ }
1424
+ resize(id, size) {
1425
+ const surface2 = this.#surfaces.get(id);
1426
+ if (!surface2) return;
1427
+ const changed = renderedSizeChanged(surface2, size);
1428
+ Object.assign(surface2, size);
1429
+ if (!changed) return;
1430
+ surface2.canvas.width = Math.max(1, Math.round(size.width * size.dpr));
1431
+ surface2.canvas.height = Math.max(1, Math.round(size.height * size.dpr));
1432
+ this.#configure(surface2);
1433
+ }
1434
+ #configure(surface2) {
1435
+ surface2.context.configure({ device: this.device, format: this.format, alphaMode: "premultiplied" });
1436
+ surface2.sceneTexture.destroy();
1437
+ surface2.sceneTexture = this.#createSceneTexture(surface2.canvas.width, surface2.canvas.height);
1438
+ surface2.postProcessBindGroup = this.#createPostProcessBindGroup(surface2.sceneTexture);
1439
+ clearGeometryCache(surface2);
1440
+ surface2.sceneValid = false;
1441
+ }
1442
+ #createSceneTexture(width, height) {
1443
+ return this.device.createTexture({
1444
+ label: "terminal scene before Ghostty custom shaders",
1445
+ size: [Math.max(1, width), Math.max(1, height)],
1446
+ format: this.format,
1447
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
1448
+ });
1449
+ }
1450
+ #createPostProcessBindGroup(texture) {
1451
+ return this.device.createBindGroup({
1452
+ label: "Ghostty custom shader bindings",
1453
+ layout: this.#postProcessPipeline.getBindGroupLayout(0),
1454
+ entries: [
1455
+ { binding: 0, resource: texture.createView() },
1456
+ { binding: 1, resource: this.#postProcessSampler }
1457
+ ]
1458
+ });
1459
+ }
1460
+ render(id, view) {
1461
+ if (!this.#surfaces.has(id)) return;
1462
+ const encoder = this.device.createCommandEncoder({ label: `terminal frame ${id}` });
1463
+ const metrics = view.damage?.geometryChanged === false ? this.#encodeCachedRender(id, view, encoder) : this.#encodeRender(id, view, encoder);
1464
+ this.device.queue.submit([encoder.finish()]);
1465
+ if (metrics) metrics.queueSubmits = 1;
1466
+ return metrics;
1467
+ }
1468
+ renderBatch(entries) {
1469
+ const active = entries.filter(({ id }) => this.#surfaces.has(id));
1470
+ if (active.length === 0) return entries.map(() => void 0);
1471
+ const encoder = this.device.createCommandEncoder({ label: `terminal frame batch (${active.length} panes)` });
1472
+ const byId = /* @__PURE__ */ new Map();
1473
+ for (const { id, view } of active) {
1474
+ byId.set(
1475
+ id,
1476
+ view.damage?.geometryChanged === false ? this.#encodeCachedRender(id, view, encoder) : this.#encodeRender(id, view, encoder)
1477
+ );
1478
+ }
1479
+ this.device.queue.submit([encoder.finish()]);
1480
+ const firstMetrics = [...byId.values()].find((metrics) => metrics !== void 0);
1481
+ if (firstMetrics) firstMetrics.queueSubmits = 1;
1482
+ return entries.map(({ id }) => byId.get(id));
1483
+ }
1484
+ #buildGeometry(view, renderRows, rowCount, fullRedraw, hasNativeRows, scale, viewportWidth, viewportHeight) {
1485
+ const rectangleVertices = [];
1486
+ const glyphVertices = [];
1487
+ const colorGlyphVertices = [];
1488
+ const fallbackGlyphVertices = [];
1489
+ const resolvedStyles = /* @__PURE__ */ new Map();
1490
+ const styleFor = (styleId) => {
1491
+ const cached = resolvedStyles.get(styleId);
1492
+ if (cached) return cached;
1493
+ const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme);
1494
+ resolvedStyles.set(styleId, resolved);
1495
+ return resolved;
1496
+ };
1497
+ const monoDefinitions = /* @__PURE__ */ new Map();
1498
+ const colorDefinitions = /* @__PURE__ */ new Map();
1499
+ for (const row of renderRows) {
1500
+ for (const glyph of view.nativeRows[row] ?? []) {
1501
+ const definition = view.glyphDefinitions.get(glyph.glyphId);
1502
+ if (!definition) continue;
1503
+ (definition.format === GlyphFormat.Rgba8Premultiplied ? colorDefinitions : monoDefinitions).set(
1504
+ definition.id,
1505
+ definition
1506
+ );
1507
+ }
1508
+ }
1509
+ this.#monoAtlas.prepare(monoDefinitions.values());
1510
+ this.#colorAtlas.prepare(colorDefinitions.values());
1511
+ if (!fullRedraw) {
1512
+ for (const row of renderRows) {
1513
+ const top = row === 0 ? 0 : (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1514
+ const bottom = row === rowCount - 1 ? viewportHeight : Math.min(viewportHeight, (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale);
1515
+ pushRectangle(
1516
+ rectangleVertices,
1517
+ 0,
1518
+ top,
1519
+ viewportWidth,
1520
+ Math.max(0, bottom - top),
1521
+ // The rectangle pipeline blends. Force the row reset opaque so it
1522
+ // replaces stale scene pixels even when a theme carries alpha.
1523
+ [view.theme.background[0], view.theme.background[1], view.theme.background[2], 1],
1524
+ viewportWidth,
1525
+ viewportHeight
1526
+ );
1527
+ }
1528
+ }
1529
+ for (const row of renderRows) {
1530
+ for (const run of view.nativeStyleRows[row] ?? []) {
1531
+ const style = styleFor(run.styleId);
1532
+ if (!style.background) continue;
1533
+ pushRectangle(
1534
+ rectangleVertices,
1535
+ (ORIGIN_X + run.cellStart * CELL_WIDTH) * scale,
1536
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1537
+ run.cellSpan * CELL_WIDTH * scale,
1538
+ LINE_HEIGHT * scale,
1539
+ style.background,
1540
+ viewportWidth,
1541
+ viewportHeight
1542
+ );
1543
+ }
1544
+ }
1545
+ const backgroundInstanceCount = rectangleVertices.length / 10;
1546
+ const selection = ordered2(view.selection);
1547
+ if (selection) {
1548
+ const [start, end] = selection;
1549
+ for (const row of renderRows) {
1550
+ if (row < start.row || row > end.row) continue;
1551
+ const first = row === start.row ? start.column : 0;
1552
+ const last = row === end.row ? end.column : Math.max(0, cellLength(view.rows[row] ?? "") - 1);
1553
+ pushRectangle(
1554
+ rectangleVertices,
1555
+ (ORIGIN_X + first * CELL_WIDTH) * scale,
1556
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1557
+ Math.max(1, last - first + 1) * CELL_WIDTH * scale,
1558
+ LINE_HEIGHT * scale,
1559
+ view.theme.selection,
1560
+ viewportWidth,
1561
+ viewportHeight
1562
+ );
1563
+ }
1564
+ }
1565
+ const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
1566
+ if (!hasNativeRows) {
1567
+ this.#fallbackAtlas.prepare(
1568
+ renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")),
1569
+ scale
1570
+ );
1571
+ }
1572
+ if (hasNativeRows) {
1573
+ for (const row of renderRows) {
1574
+ const { box: boxCells, block: blockCells } = specialDrawingCells(view.rows[row] ?? "");
1575
+ for (const instance of view.nativeRows[row] ?? []) {
1576
+ const definition = view.glyphDefinitions.get(instance.glyphId);
1577
+ if (!definition) continue;
1578
+ const style = styleFor(instance.styleId);
1579
+ if (style.invisible) continue;
1580
+ const isSelected = selectionContains(selection, row, instance.cellStart);
1581
+ const foreground = isSelected ? view.theme.selectionForeground : style.foreground;
1582
+ const boxDrawing = boxCells.get(instance.cellStart);
1583
+ if (boxDrawing) {
1584
+ let backdrop = style.background ?? view.theme.background;
1585
+ if (isSelected) backdrop = view.theme.selection;
1586
+ pushBoxDrawing(
1587
+ rectangleVertices,
1588
+ boxDrawing,
1589
+ instance.cellStart,
1590
+ row,
1591
+ scale,
1592
+ over(foreground, backdrop),
1593
+ viewportWidth,
1594
+ viewportHeight
1595
+ );
1596
+ continue;
1597
+ }
1598
+ const blockElement = blockCells.get(instance.cellStart);
1599
+ if (blockElement) {
1600
+ let backdrop = style.background ?? view.theme.background;
1601
+ if (isSelected) backdrop = view.theme.selection;
1602
+ if (pushBlockElement(
1603
+ rectangleVertices,
1604
+ blockElement,
1605
+ instance.cellStart,
1606
+ row,
1607
+ scale,
1608
+ over(foreground, backdrop),
1609
+ viewportWidth,
1610
+ viewportHeight
1611
+ ))
1612
+ continue;
1613
+ }
1614
+ const atlas = definition.format === GlyphFormat.Alpha8 ? this.#monoAtlas : this.#colorAtlas;
1615
+ const vertices = definition.format === GlyphFormat.Alpha8 ? glyphVertices : colorGlyphVertices;
1616
+ pushGlyph(
1617
+ vertices,
1618
+ (ORIGIN_X + instance.x) * scale,
1619
+ (ORIGIN_Y + row * LINE_HEIGHT + instance.y) * scale,
1620
+ instance.width * scale,
1621
+ instance.height * scale,
1622
+ atlas.glyph(definition),
1623
+ foreground,
1624
+ viewportWidth,
1625
+ viewportHeight
1626
+ );
1627
+ }
1628
+ }
1629
+ } else {
1630
+ for (const row of renderRows) {
1631
+ let column = 0;
1632
+ for (const grapheme of splitGraphemes(view.rows[row] ?? "")) {
1633
+ const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
1634
+ const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
1635
+ if (glyph) {
1636
+ const foreground = selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
1637
+ pushGlyph(
1638
+ fallbackGlyphVertices,
1639
+ (ORIGIN_X + column * CELL_WIDTH) * scale,
1640
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1641
+ CELL_WIDTH * cells * scale,
1642
+ LINE_HEIGHT * scale,
1643
+ glyph,
1644
+ foreground,
1645
+ viewportWidth,
1646
+ viewportHeight
1647
+ );
1648
+ }
1649
+ column += cells;
1650
+ }
1651
+ }
1652
+ }
1653
+ for (const row of renderRows) {
1654
+ for (const run of view.nativeStyleRows[row] ?? []) {
1655
+ const style = styleFor(run.styleId);
1656
+ if (style.invisible) continue;
1657
+ const x = (ORIGIN_X + run.cellStart * CELL_WIDTH) * scale;
1658
+ const width = run.cellSpan * CELL_WIDTH * scale;
1659
+ const rowTop = (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1660
+ const stroke = Math.max(1, Math.round(scale));
1661
+ if (style.underline) {
1662
+ pushRectangle(
1663
+ rectangleVertices,
1664
+ x,
1665
+ Math.round(rowTop + 16 * scale),
1666
+ width,
1667
+ stroke,
1668
+ style.foreground,
1669
+ viewportWidth,
1670
+ viewportHeight
1671
+ );
1672
+ }
1673
+ if (style.strikethrough) {
1674
+ pushRectangle(
1675
+ rectangleVertices,
1676
+ x,
1677
+ Math.round(rowTop + 9 * scale),
1678
+ width,
1679
+ stroke,
1680
+ style.foreground,
1681
+ viewportWidth,
1682
+ viewportHeight
1683
+ );
1684
+ }
1685
+ }
1686
+ }
1687
+ const decorationInstanceStart = backgroundInstanceCount + selectionInstanceCount;
1688
+ const decorationInstanceCount = rectangleVertices.length / 10 - decorationInstanceStart;
1689
+ return {
1690
+ rectangleData: new Float32Array(rectangleVertices),
1691
+ glyphData: new Float32Array(glyphVertices),
1692
+ colorGlyphData: new Float32Array(colorGlyphVertices),
1693
+ fallbackGlyphData: new Float32Array(fallbackGlyphVertices),
1694
+ backgroundInstanceCount,
1695
+ selectionInstanceCount,
1696
+ decorationInstanceStart,
1697
+ decorationInstanceCount,
1698
+ rectangleInstanceCount: rectangleVertices.length / 10,
1699
+ glyphInstanceCount: glyphVertices.length / 12,
1700
+ colorGlyphInstanceCount: colorGlyphVertices.length / 12,
1701
+ fallbackGlyphInstanceCount: fallbackGlyphVertices.length / 12
1702
+ };
1703
+ }
1704
+ #encodeRender(id, view, encoder) {
1705
+ const surface2 = this.#surfaces.get(id);
1706
+ if (!surface2) return;
1707
+ const monoUploadsBefore = this.#performanceMeasurementEnabled ? this.#monoAtlas.uploadMetrics() : void 0;
1708
+ const colorUploadsBefore = this.#performanceMeasurementEnabled ? this.#colorAtlas.uploadMetrics() : void 0;
1709
+ const fallbackUploadsBefore = this.#performanceMeasurementEnabled ? this.#fallbackAtlas.uploadMetrics() : void 0;
1710
+ const scale = surface2.dpr;
1711
+ const viewportWidth = surface2.canvas.width;
1712
+ const viewportHeight = surface2.canvas.height;
1713
+ const rowCount = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
1714
+ const { full: fullRedraw, rows: renderRows } = rowsForDamage(rowCount, view.damage, surface2.sceneValid);
1715
+ const renderRowSet = new Set(renderRows);
1716
+ const rectangleVertices = [];
1717
+ const glyphVertices = [];
1718
+ const colorGlyphVertices = [];
1719
+ const fallbackGlyphVertices = [];
1720
+ const resolvedStyles = /* @__PURE__ */ new Map();
1721
+ const styleFor = (styleId) => {
1722
+ const cached = resolvedStyles.get(styleId);
1723
+ if (cached) return cached;
1724
+ const resolved = resolveStyle(view.styleDefinitions.get(styleId), view.theme);
1725
+ resolvedStyles.set(styleId, resolved);
1726
+ return resolved;
1727
+ };
1728
+ const monoDefinitions = /* @__PURE__ */ new Map();
1729
+ const colorDefinitions = /* @__PURE__ */ new Map();
1730
+ for (const row of renderRows) {
1731
+ for (const glyph of view.nativeRows[row] ?? []) {
1732
+ const definition = view.glyphDefinitions.get(glyph.glyphId);
1733
+ if (!definition) continue;
1734
+ (definition.format === GlyphFormat.Rgba8Premultiplied ? colorDefinitions : monoDefinitions).set(
1735
+ definition.id,
1736
+ definition
1737
+ );
1738
+ }
1739
+ }
1740
+ this.#monoAtlas.prepare(monoDefinitions.values());
1741
+ this.#colorAtlas.prepare(colorDefinitions.values());
1742
+ if (!fullRedraw) {
1743
+ for (const row of renderRows) {
1744
+ const top = row === 0 ? 0 : (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1745
+ const bottom = row === rowCount - 1 ? viewportHeight : Math.min(viewportHeight, (ORIGIN_Y + (row + 1) * LINE_HEIGHT) * scale);
1746
+ pushRectangle(
1747
+ rectangleVertices,
1748
+ 0,
1749
+ top,
1750
+ viewportWidth,
1751
+ Math.max(0, bottom - top),
1752
+ // The rectangle pipeline blends. Force the row reset opaque so it
1753
+ // replaces stale scene pixels even when a theme carries alpha.
1754
+ [view.theme.background[0], view.theme.background[1], view.theme.background[2], 1],
1755
+ viewportWidth,
1756
+ viewportHeight
1757
+ );
1758
+ }
1759
+ }
1760
+ for (const row of renderRows) {
1761
+ for (const run of view.nativeStyleRows[row] ?? []) {
1762
+ const style = styleFor(run.styleId);
1763
+ if (!style.background) continue;
1764
+ pushRectangle(
1765
+ rectangleVertices,
1766
+ (ORIGIN_X + run.cellStart * CELL_WIDTH) * scale,
1767
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1768
+ run.cellSpan * CELL_WIDTH * scale,
1769
+ LINE_HEIGHT * scale,
1770
+ style.background,
1771
+ viewportWidth,
1772
+ viewportHeight
1773
+ );
1774
+ }
1775
+ }
1776
+ const backgroundInstanceCount = rectangleVertices.length / 10;
1777
+ const selection = ordered2(view.selection);
1778
+ if (selection) {
1779
+ const [start, end] = selection;
1780
+ for (const row of renderRows) {
1781
+ if (row < start.row || row > end.row) continue;
1782
+ const first = row === start.row ? start.column : 0;
1783
+ const last = row === end.row ? end.column : Math.max(0, cellLength(view.rows[row] ?? "") - 1);
1784
+ pushRectangle(
1785
+ rectangleVertices,
1786
+ (ORIGIN_X + first * CELL_WIDTH) * scale,
1787
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1788
+ Math.max(1, last - first + 1) * CELL_WIDTH * scale,
1789
+ LINE_HEIGHT * scale,
1790
+ view.theme.selection,
1791
+ viewportWidth,
1792
+ viewportHeight
1793
+ );
1794
+ }
1795
+ }
1796
+ const selectionInstanceCount = rectangleVertices.length / 10 - backgroundInstanceCount;
1797
+ const hasNativeRows = view.nativeRows.some((row) => row.length > 0);
1798
+ if (!hasNativeRows) {
1799
+ this.#fallbackAtlas.prepare(
1800
+ renderRows.flatMap((row) => splitGraphemes(view.rows[row] ?? "")),
1801
+ scale
1802
+ );
1803
+ }
1804
+ if (hasNativeRows) {
1805
+ for (const row of renderRows) {
1806
+ const { box: boxCells, block: blockCells } = specialDrawingCells(view.rows[row] ?? "");
1807
+ for (const instance of view.nativeRows[row] ?? []) {
1808
+ const definition = view.glyphDefinitions.get(instance.glyphId);
1809
+ if (!definition) continue;
1810
+ const style = styleFor(instance.styleId);
1811
+ if (style.invisible) continue;
1812
+ const isSelected = selectionContains(selection, row, instance.cellStart);
1813
+ const foreground = isSelected ? view.theme.selectionForeground : style.foreground;
1814
+ const boxDrawing = boxCells.get(instance.cellStart);
1815
+ if (boxDrawing) {
1816
+ let backdrop = style.background ?? view.theme.background;
1817
+ if (isSelected) backdrop = view.theme.selection;
1818
+ const boxColor = over(foreground, backdrop);
1819
+ pushBoxDrawing(
1820
+ rectangleVertices,
1821
+ boxDrawing,
1822
+ instance.cellStart,
1823
+ row,
1824
+ scale,
1825
+ boxColor,
1826
+ viewportWidth,
1827
+ viewportHeight
1828
+ );
1829
+ continue;
1830
+ }
1831
+ const blockElement = blockCells.get(instance.cellStart);
1832
+ if (blockElement) {
1833
+ let backdrop = style.background ?? view.theme.background;
1834
+ if (isSelected) backdrop = view.theme.selection;
1835
+ const blockColor = over(foreground, backdrop);
1836
+ if (pushBlockElement(
1837
+ rectangleVertices,
1838
+ blockElement,
1839
+ instance.cellStart,
1840
+ row,
1841
+ scale,
1842
+ blockColor,
1843
+ viewportWidth,
1844
+ viewportHeight
1845
+ ))
1846
+ continue;
1847
+ }
1848
+ const atlas = definition.format === GlyphFormat.Alpha8 ? this.#monoAtlas : this.#colorAtlas;
1849
+ const vertices = definition.format === GlyphFormat.Alpha8 ? glyphVertices : colorGlyphVertices;
1850
+ pushGlyph(
1851
+ vertices,
1852
+ (ORIGIN_X + instance.x) * scale,
1853
+ (ORIGIN_Y + row * LINE_HEIGHT + instance.y) * scale,
1854
+ instance.width * scale,
1855
+ instance.height * scale,
1856
+ atlas.glyph(definition),
1857
+ foreground,
1858
+ viewportWidth,
1859
+ viewportHeight
1860
+ );
1861
+ }
1862
+ }
1863
+ } else {
1864
+ for (const row of renderRows) {
1865
+ let column = 0;
1866
+ for (const grapheme of splitGraphemes(view.rows[row] ?? "")) {
1867
+ const glyph = this.#fallbackAtlas.glyph(grapheme, scale);
1868
+ const cells = glyph?.cells ?? graphemeCellWidth(grapheme);
1869
+ if (glyph) {
1870
+ const foreground = selectionContains(selection, row, column) ? view.theme.selectionForeground : view.theme.foreground;
1871
+ pushGlyph(
1872
+ fallbackGlyphVertices,
1873
+ (ORIGIN_X + column * CELL_WIDTH) * scale,
1874
+ (ORIGIN_Y + row * LINE_HEIGHT) * scale,
1875
+ CELL_WIDTH * cells * scale,
1876
+ LINE_HEIGHT * scale,
1877
+ glyph,
1878
+ foreground,
1879
+ viewportWidth,
1880
+ viewportHeight
1881
+ );
1882
+ }
1883
+ column += cells;
1884
+ }
1885
+ }
1886
+ }
1887
+ for (const row of renderRows) {
1888
+ for (const run of view.nativeStyleRows[row] ?? []) {
1889
+ const style = styleFor(run.styleId);
1890
+ if (style.invisible) continue;
1891
+ const x = (ORIGIN_X + run.cellStart * CELL_WIDTH) * scale;
1892
+ const width = run.cellSpan * CELL_WIDTH * scale;
1893
+ const rowTop = (ORIGIN_Y + row * LINE_HEIGHT) * scale;
1894
+ const stroke = Math.max(1, Math.round(scale));
1895
+ if (style.underline) {
1896
+ pushRectangle(
1897
+ rectangleVertices,
1898
+ x,
1899
+ Math.round(rowTop + 16 * scale),
1900
+ width,
1901
+ stroke,
1902
+ style.foreground,
1903
+ viewportWidth,
1904
+ viewportHeight
1905
+ );
1906
+ }
1907
+ if (style.strikethrough) {
1908
+ pushRectangle(
1909
+ rectangleVertices,
1910
+ x,
1911
+ Math.round(rowTop + 9 * scale),
1912
+ width,
1913
+ stroke,
1914
+ style.foreground,
1915
+ viewportWidth,
1916
+ viewportHeight
1917
+ );
1918
+ }
1919
+ }
1920
+ }
1921
+ const decorationInstanceStart = backgroundInstanceCount + selectionInstanceCount;
1922
+ const decorationInstanceCount = rectangleVertices.length / 10 - decorationInstanceStart;
1923
+ const cursorStyle = effectiveCursorStyle(view);
1924
+ if (cursorStyle !== null && renderRowSet.has(view.cursor.y)) {
1925
+ const x = (ORIGIN_X + view.cursor.x * CELL_WIDTH) * scale;
1926
+ const y = (ORIGIN_Y + view.cursor.y * LINE_HEIGHT) * scale;
1927
+ const width = CELL_WIDTH * scale;
1928
+ const height = LINE_HEIGHT * scale;
1929
+ const stroke = Math.max(1, Math.round(scale));
1930
+ const cursorColor = cursorStyle === CursorStyle.HollowBlock ? [view.theme.cursor[0], view.theme.cursor[1], view.theme.cursor[2], 1] : view.theme.cursor;
1931
+ if (cursorStyle === CursorStyle.Bar) {
1932
+ pushRectangle(
1933
+ rectangleVertices,
1934
+ x,
1935
+ y,
1936
+ Math.max(2, Math.round(2 * scale)),
1937
+ height,
1938
+ cursorColor,
1939
+ viewportWidth,
1940
+ viewportHeight
1941
+ );
1942
+ } else if (cursorStyle === CursorStyle.Underline) {
1943
+ const thickness = Math.max(2, Math.round(2 * scale));
1944
+ pushRectangle(
1945
+ rectangleVertices,
1946
+ x,
1947
+ y + height - thickness,
1948
+ width,
1949
+ thickness,
1950
+ cursorColor,
1951
+ viewportWidth,
1952
+ viewportHeight
1953
+ );
1954
+ } else if (cursorStyle === CursorStyle.HollowBlock) {
1955
+ pushRectangle(rectangleVertices, x, y, width, stroke, cursorColor, viewportWidth, viewportHeight);
1956
+ pushRectangle(
1957
+ rectangleVertices,
1958
+ x,
1959
+ y + height - stroke,
1960
+ width,
1961
+ stroke,
1962
+ cursorColor,
1963
+ viewportWidth,
1964
+ viewportHeight
1965
+ );
1966
+ pushRectangle(
1967
+ rectangleVertices,
1968
+ x,
1969
+ y + stroke,
1970
+ stroke,
1971
+ Math.max(0, height - stroke * 2),
1972
+ cursorColor,
1973
+ viewportWidth,
1974
+ viewportHeight
1975
+ );
1976
+ pushRectangle(
1977
+ rectangleVertices,
1978
+ x + width - stroke,
1979
+ y + stroke,
1980
+ stroke,
1981
+ Math.max(0, height - stroke * 2),
1982
+ cursorColor,
1983
+ viewportWidth,
1984
+ viewportHeight
1985
+ );
1986
+ } else {
1987
+ pushRectangle(rectangleVertices, x, y, width, height, cursorColor, viewportWidth, viewportHeight);
1988
+ }
1989
+ }
1990
+ const rectangleData = new Float32Array(rectangleVertices);
1991
+ const glyphData = new Float32Array(glyphVertices);
1992
+ const colorGlyphData = new Float32Array(colorGlyphVertices);
1993
+ const fallbackGlyphData = new Float32Array(fallbackGlyphVertices);
1994
+ const rectangleBuffer = surface2.rectangleBuffer.write(rectangleData);
1995
+ const glyphBuffer = surface2.glyphBuffer.write(glyphData);
1996
+ const colorGlyphBuffer = surface2.colorGlyphBuffer.write(colorGlyphData);
1997
+ const fallbackGlyphBuffer = surface2.fallbackGlyphBuffer.write(fallbackGlyphData);
1998
+ const cursorInstanceStart = decorationInstanceStart + decorationInstanceCount;
1999
+ const cursorInstanceCount = rectangleVertices.length / 10 - cursorInstanceStart;
2000
+ const pass = encoder.beginRenderPass({
2001
+ label: `terminal pass ${id}`,
2002
+ colorAttachments: [
2003
+ {
2004
+ view: surface2.sceneTexture.createView(),
2005
+ clearValue: {
2006
+ r: view.theme.background[0],
2007
+ g: view.theme.background[1],
2008
+ b: view.theme.background[2],
2009
+ a: view.theme.background[3]
2010
+ },
2011
+ loadOp: fullRedraw ? "clear" : "load",
2012
+ storeOp: "store"
2013
+ }
2014
+ ]
2015
+ });
2016
+ if (rectangleBuffer && backgroundInstanceCount > 0) {
2017
+ pass.setPipeline(this.#rectanglePipeline);
2018
+ pass.setVertexBuffer(0, rectangleBuffer);
2019
+ pass.draw(6, backgroundInstanceCount);
2020
+ }
2021
+ if (rectangleBuffer && selectionInstanceCount > 0) {
2022
+ pass.setPipeline(this.#rectanglePipeline);
2023
+ pass.setVertexBuffer(0, rectangleBuffer);
2024
+ pass.draw(6, selectionInstanceCount, 0, backgroundInstanceCount);
2025
+ }
2026
+ if (glyphBuffer && glyphData.length > 0) {
2027
+ pass.setPipeline(this.#glyphPipeline);
2028
+ pass.setBindGroup(0, this.#monoAtlas.bindGroup);
2029
+ pass.setVertexBuffer(0, glyphBuffer);
2030
+ pass.draw(6, glyphVertices.length / 12);
2031
+ }
2032
+ if (colorGlyphBuffer && colorGlyphData.length > 0) {
2033
+ pass.setPipeline(this.#colorGlyphPipeline);
2034
+ pass.setBindGroup(0, this.#colorAtlas.bindGroup);
2035
+ pass.setVertexBuffer(0, colorGlyphBuffer);
2036
+ pass.draw(6, colorGlyphVertices.length / 12);
2037
+ }
2038
+ if (fallbackGlyphBuffer && fallbackGlyphData.length > 0) {
2039
+ pass.setPipeline(this.#fallbackGlyphPipeline);
2040
+ pass.setBindGroup(0, this.#fallbackAtlas.bindGroup);
2041
+ pass.setVertexBuffer(0, fallbackGlyphBuffer);
2042
+ pass.draw(6, fallbackGlyphVertices.length / 12);
2043
+ }
2044
+ if (rectangleBuffer && decorationInstanceCount > 0) {
2045
+ pass.setPipeline(this.#rectanglePipeline);
2046
+ pass.setVertexBuffer(0, rectangleBuffer);
2047
+ pass.draw(6, decorationInstanceCount, 0, decorationInstanceStart);
2048
+ }
2049
+ if (rectangleBuffer && cursorInstanceCount > 0) {
2050
+ pass.setPipeline(this.#rectanglePipeline);
2051
+ pass.setVertexBuffer(0, rectangleBuffer);
2052
+ pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
2053
+ }
2054
+ pass.end();
2055
+ const postProcessPass = encoder.beginRenderPass({
2056
+ label: `Ghostty custom shader pass ${id}`,
2057
+ colorAttachments: [
2058
+ {
2059
+ view: surface2.context.getCurrentTexture().createView(),
2060
+ clearValue: {
2061
+ r: view.theme.background[0],
2062
+ g: view.theme.background[1],
2063
+ b: view.theme.background[2],
2064
+ a: 1
2065
+ },
2066
+ loadOp: "clear",
2067
+ storeOp: "store"
2068
+ }
2069
+ ]
2070
+ });
2071
+ postProcessPass.setPipeline(this.#postProcessPipeline);
2072
+ postProcessPass.setBindGroup(0, surface2.postProcessBindGroup);
2073
+ postProcessPass.draw(3);
2074
+ postProcessPass.end();
2075
+ surface2.sceneValid = true;
2076
+ if (!this.#performanceMeasurementEnabled) return void 0;
2077
+ const monoUploadsAfter = this.#monoAtlas.uploadMetrics();
2078
+ const colorUploadsAfter = this.#colorAtlas.uploadMetrics();
2079
+ const fallbackUploadsAfter = this.#fallbackAtlas.uploadMetrics();
2080
+ return {
2081
+ queueSubmits: 0,
2082
+ fullRenders: Number(fullRedraw),
2083
+ partialRenders: Number(!fullRedraw),
2084
+ damagedRows: renderRows.length,
2085
+ geometryCacheHits: 0,
2086
+ geometryCacheMisses: 0,
2087
+ canvasPixels: viewportWidth * viewportHeight,
2088
+ renderPasses: 2,
2089
+ drawCalls: Number(backgroundInstanceCount > 0) + Number(selectionInstanceCount > 0) + Number(glyphData.length > 0) + Number(colorGlyphData.length > 0) + Number(fallbackGlyphData.length > 0) + Number(decorationInstanceCount > 0) + Number(cursorInstanceCount > 0) + 1,
2090
+ rectangleVertices: rectangleVertices.length / 10 * 6,
2091
+ monoGlyphVertices: glyphVertices.length / 12 * 6,
2092
+ colorGlyphVertices: colorGlyphVertices.length / 12 * 6,
2093
+ fallbackGlyphVertices: fallbackGlyphVertices.length / 12 * 6,
2094
+ vertexUploadBytes: rectangleData.byteLength + glyphData.byteLength + colorGlyphData.byteLength + fallbackGlyphData.byteLength,
2095
+ atlasUploadBytes: monoUploadsAfter.bytes - monoUploadsBefore.bytes + (colorUploadsAfter.bytes - colorUploadsBefore.bytes) + (fallbackUploadsAfter.bytes - fallbackUploadsBefore.bytes),
2096
+ atlasUploadCalls: monoUploadsAfter.calls - monoUploadsBefore.calls + (colorUploadsAfter.calls - colorUploadsBefore.calls) + (fallbackUploadsAfter.calls - fallbackUploadsBefore.calls)
2097
+ };
2098
+ }
2099
+ #encodeCachedRender(id, view, encoder) {
2100
+ const surface2 = this.#surfaces.get(id);
2101
+ if (!surface2) return;
2102
+ if (!surface2.sceneValid || !view.damage || view.damage.full) return this.#encodeRender(id, view, encoder);
2103
+ const rowCount = Math.max(view.rows.length, view.nativeRows.length, view.nativeStyleRows.length);
2104
+ const damage = rowsForDamage(rowCount, view.damage, surface2.sceneValid);
2105
+ const monoUploadsBefore = this.#performanceMeasurementEnabled ? this.#monoAtlas.uploadMetrics() : void 0;
2106
+ const colorUploadsBefore = this.#performanceMeasurementEnabled ? this.#colorAtlas.uploadMetrics() : void 0;
2107
+ const fallbackUploadsBefore = this.#performanceMeasurementEnabled ? this.#fallbackAtlas.uploadMetrics() : void 0;
2108
+ const scale = surface2.dpr;
2109
+ const viewportWidth = surface2.canvas.width;
2110
+ const viewportHeight = surface2.canvas.height;
2111
+ const renderRowSet = new Set(damage.rows);
2112
+ const hasNativeRows = view.nativeRows.some((row) => row.length > 0);
2113
+ let geometry;
2114
+ let geometryUploadBytes = 0;
2115
+ const generations = () => [
2116
+ this.#monoAtlas.generation,
2117
+ this.#colorAtlas.generation,
2118
+ this.#fallbackAtlas.generation
2119
+ ];
2120
+ let key = geometryCacheKey(view, damage.rows, hasNativeRows, generations());
2121
+ const cached = surface2.geometryCache.get(key);
2122
+ const cacheHit = cached !== void 0;
2123
+ if (cached) {
2124
+ geometry = cached;
2125
+ surface2.geometryCache.delete(key);
2126
+ surface2.geometryCache.set(key, cached);
2127
+ } else {
2128
+ const admitted = surface2.geometryCandidates.has(key);
2129
+ const candidateKey = key;
2130
+ const cpu = this.#buildGeometry(
2131
+ view,
2132
+ damage.rows,
2133
+ rowCount,
2134
+ false,
2135
+ hasNativeRows,
2136
+ scale,
2137
+ viewportWidth,
2138
+ viewportHeight
2139
+ );
2140
+ key = geometryCacheKey(view, damage.rows, hasNativeRows, generations());
2141
+ const promote = admitted && key === candidateKey;
2142
+ geometry = {
2143
+ rectangleBuffer: promote ? createCachedVertexBuffer(this.device, `cached rectangles ${id}`, cpu.rectangleData) : surface2.rectangleBuffer.write(cpu.rectangleData),
2144
+ glyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached glyphs ${id}`, cpu.glyphData) : surface2.glyphBuffer.write(cpu.glyphData),
2145
+ colorGlyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached color glyphs ${id}`, cpu.colorGlyphData) : surface2.colorGlyphBuffer.write(cpu.colorGlyphData),
2146
+ fallbackGlyphBuffer: promote ? createCachedVertexBuffer(this.device, `cached fallback glyphs ${id}`, cpu.fallbackGlyphData) : surface2.fallbackGlyphBuffer.write(cpu.fallbackGlyphData),
2147
+ backgroundInstanceCount: cpu.backgroundInstanceCount,
2148
+ selectionInstanceCount: cpu.selectionInstanceCount,
2149
+ decorationInstanceStart: cpu.decorationInstanceStart,
2150
+ decorationInstanceCount: cpu.decorationInstanceCount,
2151
+ rectangleInstanceCount: cpu.rectangleInstanceCount,
2152
+ glyphInstanceCount: cpu.glyphInstanceCount,
2153
+ colorGlyphInstanceCount: cpu.colorGlyphInstanceCount,
2154
+ fallbackGlyphInstanceCount: cpu.fallbackGlyphInstanceCount
2155
+ };
2156
+ geometryUploadBytes = cpu.rectangleData.byteLength + cpu.glyphData.byteLength + cpu.colorGlyphData.byteLength + cpu.fallbackGlyphData.byteLength;
2157
+ if (promote) {
2158
+ surface2.geometryCandidates.delete(key);
2159
+ surface2.geometryCache.set(key, geometry);
2160
+ while (surface2.geometryCache.size > GEOMETRY_CACHE_LIMIT) {
2161
+ const oldestKey = surface2.geometryCache.keys().next().value;
2162
+ if (oldestKey === void 0) break;
2163
+ const oldest = surface2.geometryCache.get(oldestKey);
2164
+ if (oldest) destroyCachedGeometry(oldest);
2165
+ surface2.geometryCache.delete(oldestKey);
2166
+ }
2167
+ } else {
2168
+ surface2.geometryCandidates.delete(key);
2169
+ surface2.geometryCandidates.set(key, true);
2170
+ while (surface2.geometryCandidates.size > GEOMETRY_CACHE_LIMIT) {
2171
+ const oldestKey = surface2.geometryCandidates.keys().next().value;
2172
+ if (oldestKey === void 0) break;
2173
+ surface2.geometryCandidates.delete(oldestKey);
2174
+ }
2175
+ }
2176
+ }
2177
+ const cursorVertices = [];
2178
+ pushCursorVertices(cursorVertices, view, renderRowSet, scale, viewportWidth, viewportHeight);
2179
+ const cursorData = new Float32Array(cursorVertices);
2180
+ const cursorBuffer = surface2.cursorBuffer.write(cursorData);
2181
+ const cursorInstanceStart = 0;
2182
+ const cursorInstanceCount = cursorData.length / 10;
2183
+ const pass = encoder.beginRenderPass({
2184
+ label: `terminal pass ${id}`,
2185
+ colorAttachments: [
2186
+ {
2187
+ view: surface2.sceneTexture.createView(),
2188
+ clearValue: {
2189
+ r: view.theme.background[0],
2190
+ g: view.theme.background[1],
2191
+ b: view.theme.background[2],
2192
+ a: view.theme.background[3]
2193
+ },
2194
+ loadOp: "load",
2195
+ storeOp: "store"
2196
+ }
2197
+ ]
2198
+ });
2199
+ if (geometry.rectangleBuffer && geometry.backgroundInstanceCount > 0) {
2200
+ pass.setPipeline(this.#rectanglePipeline);
2201
+ pass.setVertexBuffer(0, geometry.rectangleBuffer);
2202
+ pass.draw(6, geometry.backgroundInstanceCount);
2203
+ }
2204
+ if (geometry.rectangleBuffer && geometry.selectionInstanceCount > 0) {
2205
+ pass.setPipeline(this.#rectanglePipeline);
2206
+ pass.setVertexBuffer(0, geometry.rectangleBuffer);
2207
+ pass.draw(6, geometry.selectionInstanceCount, 0, geometry.backgroundInstanceCount);
2208
+ }
2209
+ if (geometry.glyphBuffer && geometry.glyphInstanceCount > 0) {
2210
+ pass.setPipeline(this.#glyphPipeline);
2211
+ pass.setBindGroup(0, this.#monoAtlas.bindGroup);
2212
+ pass.setVertexBuffer(0, geometry.glyphBuffer);
2213
+ pass.draw(6, geometry.glyphInstanceCount);
2214
+ }
2215
+ if (geometry.colorGlyphBuffer && geometry.colorGlyphInstanceCount > 0) {
2216
+ pass.setPipeline(this.#colorGlyphPipeline);
2217
+ pass.setBindGroup(0, this.#colorAtlas.bindGroup);
2218
+ pass.setVertexBuffer(0, geometry.colorGlyphBuffer);
2219
+ pass.draw(6, geometry.colorGlyphInstanceCount);
2220
+ }
2221
+ if (geometry.fallbackGlyphBuffer && geometry.fallbackGlyphInstanceCount > 0) {
2222
+ pass.setPipeline(this.#fallbackGlyphPipeline);
2223
+ pass.setBindGroup(0, this.#fallbackAtlas.bindGroup);
2224
+ pass.setVertexBuffer(0, geometry.fallbackGlyphBuffer);
2225
+ pass.draw(6, geometry.fallbackGlyphInstanceCount);
2226
+ }
2227
+ if (geometry.rectangleBuffer && geometry.decorationInstanceCount > 0) {
2228
+ pass.setPipeline(this.#rectanglePipeline);
2229
+ pass.setVertexBuffer(0, geometry.rectangleBuffer);
2230
+ pass.draw(6, geometry.decorationInstanceCount, 0, geometry.decorationInstanceStart);
2231
+ }
2232
+ if (cursorBuffer && cursorInstanceCount > 0) {
2233
+ pass.setPipeline(this.#rectanglePipeline);
2234
+ pass.setVertexBuffer(0, cursorBuffer);
2235
+ pass.draw(6, cursorInstanceCount, 0, cursorInstanceStart);
2236
+ }
2237
+ pass.end();
2238
+ const postProcessPass = encoder.beginRenderPass({
2239
+ label: `Ghostty custom shader pass ${id}`,
2240
+ colorAttachments: [
2241
+ {
2242
+ view: surface2.context.getCurrentTexture().createView(),
2243
+ clearValue: {
2244
+ r: view.theme.background[0],
2245
+ g: view.theme.background[1],
2246
+ b: view.theme.background[2],
2247
+ a: 1
2248
+ },
2249
+ loadOp: "clear",
2250
+ storeOp: "store"
2251
+ }
2252
+ ]
2253
+ });
2254
+ postProcessPass.setPipeline(this.#postProcessPipeline);
2255
+ postProcessPass.setBindGroup(0, surface2.postProcessBindGroup);
2256
+ postProcessPass.draw(3);
2257
+ postProcessPass.end();
2258
+ surface2.sceneValid = true;
2259
+ if (!this.#performanceMeasurementEnabled) return void 0;
2260
+ const monoUploadsAfter = this.#monoAtlas.uploadMetrics();
2261
+ const colorUploadsAfter = this.#colorAtlas.uploadMetrics();
2262
+ const fallbackUploadsAfter = this.#fallbackAtlas.uploadMetrics();
2263
+ return {
2264
+ queueSubmits: 0,
2265
+ fullRenders: 0,
2266
+ partialRenders: 1,
2267
+ damagedRows: damage.rows.length,
2268
+ geometryCacheHits: Number(cacheHit),
2269
+ geometryCacheMisses: Number(!cacheHit),
2270
+ canvasPixels: viewportWidth * viewportHeight,
2271
+ renderPasses: 2,
2272
+ drawCalls: Number(geometry.backgroundInstanceCount > 0) + Number(geometry.selectionInstanceCount > 0) + Number(geometry.glyphInstanceCount > 0) + Number(geometry.colorGlyphInstanceCount > 0) + Number(geometry.fallbackGlyphInstanceCount > 0) + Number(geometry.decorationInstanceCount > 0) + Number(cursorInstanceCount > 0) + 1,
2273
+ rectangleVertices: (geometry.rectangleInstanceCount + cursorInstanceCount) * 6,
2274
+ monoGlyphVertices: geometry.glyphInstanceCount * 6,
2275
+ colorGlyphVertices: geometry.colorGlyphInstanceCount * 6,
2276
+ fallbackGlyphVertices: geometry.fallbackGlyphInstanceCount * 6,
2277
+ vertexUploadBytes: geometryUploadBytes + cursorData.byteLength,
2278
+ atlasUploadBytes: monoUploadsAfter.bytes - monoUploadsBefore.bytes + (colorUploadsAfter.bytes - colorUploadsBefore.bytes) + (fallbackUploadsAfter.bytes - fallbackUploadsBefore.bytes),
2279
+ atlasUploadCalls: monoUploadsAfter.calls - monoUploadsBefore.calls + (colorUploadsAfter.calls - colorUploadsBefore.calls) + (fallbackUploadsAfter.calls - fallbackUploadsBefore.calls)
2280
+ };
2281
+ }
2282
+ settle() {
2283
+ return this.device.queue.onSubmittedWorkDone();
2284
+ }
2285
+ destroy() {
2286
+ for (const id of [...this.#surfaces.keys()]) this.unmount(id);
2287
+ this.#monoAtlas.destroy();
2288
+ this.#colorAtlas.destroy();
2289
+ this.#fallbackAtlas.destroy();
2290
+ this.device.destroy();
2291
+ }
2292
+ };
2293
+
2294
+ // src/frame-sequence.ts
2295
+ function classifyFrame(previous, incoming) {
2296
+ const sameSession = previous.sessionEpoch === 0n || incoming.sessionEpoch === previous.sessionEpoch;
2297
+ if (sameSession && (incoming.layoutEpoch < previous.layoutEpoch || incoming.layoutEpoch === previous.layoutEpoch && incoming.sequence <= previous.sequence))
2298
+ return "stale";
2299
+ if (previous.awaitingResync && !incoming.full) return "resync";
2300
+ const changedSession = !sameSession;
2301
+ const missingInitialSnapshot = previous.sessionEpoch === 0n && !incoming.full;
2302
+ const sequenceGap = previous.sessionEpoch === incoming.sessionEpoch && previous.sequence !== 0n && incoming.sequence !== previous.sequence + 1n;
2303
+ return !incoming.full && (changedSession || missingInitialSnapshot || sequenceGap) ? "resync" : "accept";
2304
+ }
2305
+
2306
+ // src/cursor-invalidation.ts
2307
+ function cursorActivityChangesPixels(cursor, focused, cursorBlinkVisible) {
2308
+ return focused && cursor.visible && cursor.blinking && !cursorBlinkVisible;
2309
+ }
2310
+
2311
+ // src/catalog-budget.ts
2312
+ function catalogAdmission(resetsCatalog, alreadyUsingFallback, fits) {
2313
+ if (alreadyUsingFallback && !resetsCatalog) return "text-fallback";
2314
+ if (fits) return "install";
2315
+ return resetsCatalog ? "text-fallback" : "request-full";
2316
+ }
2317
+ function glyphCatalogFits(sessionDefinitions, sessionPixelBytes, sharedDefinitions, sharedPixelBytes, definitions, budget) {
2318
+ if (definitions.length === 0) return true;
2319
+ let nextDefinitionCount = sessionDefinitions.size;
2320
+ let nextSharedDefinitionCount = sharedDefinitions.size;
2321
+ let nextSessionPixelBytes = sessionPixelBytes;
2322
+ let nextSharedPixelBytes = sharedPixelBytes;
2323
+ const pending = /* @__PURE__ */ new Set();
2324
+ for (const definition of definitions) {
2325
+ if (sessionDefinitions.has(definition.id) || pending.has(definition.id)) continue;
2326
+ pending.add(definition.id);
2327
+ const shared = sharedDefinitions.get(definition.id);
2328
+ const pixelBytes = shared?.definition.pixels.byteLength ?? definition.pixels.byteLength;
2329
+ nextDefinitionCount += 1;
2330
+ nextSessionPixelBytes += pixelBytes;
2331
+ if (!shared) {
2332
+ nextSharedDefinitionCount += 1;
2333
+ nextSharedPixelBytes += pixelBytes;
2334
+ }
2335
+ if (nextDefinitionCount > budget.maxDefinitions || nextSharedDefinitionCount > budget.maxSharedDefinitions || nextSessionPixelBytes > budget.maxSessionPixelBytes || nextSharedPixelBytes > budget.maxSharedPixelBytes)
2336
+ return false;
2337
+ }
2338
+ return true;
2339
+ }
2340
+ function definitionCatalogFits(installed, definitions, maxDefinitions, retainedDefinitions = installed.size, maxRetainedDefinitions = Number.MAX_SAFE_INTEGER) {
2341
+ if (definitions.length === 0) return true;
2342
+ let nextDefinitionCount = installed.size;
2343
+ let nextRetainedDefinitions = retainedDefinitions;
2344
+ const pending = /* @__PURE__ */ new Set();
2345
+ for (const definition of definitions) {
2346
+ if (installed.has(definition.id) || pending.has(definition.id)) continue;
2347
+ pending.add(definition.id);
2348
+ nextDefinitionCount += 1;
2349
+ nextRetainedDefinitions += 1;
2350
+ if (nextDefinitionCount > maxDefinitions || nextRetainedDefinitions > maxRetainedDefinitions) return false;
2351
+ }
2352
+ return true;
2353
+ }
2354
+
2355
+ // src/terminal-render.worker.ts
2356
+ var hiddenCursor = { x: 0, y: 0, visible: false, style: 1, blinking: false };
2357
+ var CURSOR_BLINK_INTERVAL_MS = 600;
2358
+ var MAX_SESSION_GLYPH_DEFINITIONS = 65536;
2359
+ var MAX_SESSION_GLYPH_PIXEL_BYTES = 64 * 1024 * 1024;
2360
+ var MAX_SESSION_STYLE_DEFINITIONS = 65536;
2361
+ var MAX_SHARED_GLYPH_DEFINITIONS = 131072;
2362
+ var MAX_SHARED_GLYPH_PIXEL_BYTES = 128 * 1024 * 1024;
2363
+ var MAX_RETAINED_STYLE_DEFINITIONS = 262144;
2364
+ var GLYPH_CATALOG_BUDGET = {
2365
+ maxDefinitions: MAX_SESSION_GLYPH_DEFINITIONS,
2366
+ maxSharedDefinitions: MAX_SHARED_GLYPH_DEFINITIONS,
2367
+ maxSessionPixelBytes: MAX_SESSION_GLYPH_PIXEL_BYTES,
2368
+ maxSharedPixelBytes: MAX_SHARED_GLYPH_PIXEL_BYTES
2369
+ };
2370
+ var NO_GLYPH_DEFINITIONS = [];
2371
+ var NO_STYLE_DEFINITIONS = [];
2372
+ var MAX_PERFORMANCE_SAMPLES = 1e5;
2373
+ var FRAME_CREDIT_BATCH_BYTES = 4 * 1024 * 1024;
2374
+ var FRAME_CREDIT_MAX_DELAY_MS = 8;
2375
+ var snapshots = /* @__PURE__ */ new Map();
2376
+ var sharedGlyphDefinitions = /* @__PURE__ */ new Map();
2377
+ var sharedGlyphPixelBytes = 0;
2378
+ var retainedStyleDefinitions = 0;
2379
+ var surfaces = /* @__PURE__ */ new Map();
2380
+ var surfaceIdsBySession = /* @__PURE__ */ new Map();
2381
+ var noSurfaceIds = /* @__PURE__ */ new Set();
2382
+ var presentationDefaults = /* @__PURE__ */ new Map();
2383
+ var mounts = /* @__PURE__ */ new Map();
2384
+ var cursorBlinkTimers = /* @__PURE__ */ new Map();
2385
+ var dirty = /* @__PURE__ */ new Set();
2386
+ var occluded = /* @__PURE__ */ new Set();
2387
+ var renderer;
2388
+ var rendererPromise;
2389
+ var flushScheduled = false;
2390
+ var recoveryAttempts = 0;
2391
+ var recovering = false;
2392
+ var nativeTextAnnounced = false;
2393
+ var forceCanvasFallback = false;
2394
+ var partialRenderingEnabled = true;
2395
+ var pendingFrameCreditBytes = 0;
2396
+ var frameCreditTimer;
2397
+ var performanceMeasurement;
2398
+ function appendPerformanceSample(samples, value) {
2399
+ if (samples.length < MAX_PERFORMANCE_SAMPLES) samples.push(value);
2400
+ }
2401
+ function flushFrameCredit() {
2402
+ if (frameCreditTimer !== void 0) clearTimeout(frameCreditTimer);
2403
+ frameCreditTimer = void 0;
2404
+ if (pendingFrameCreditBytes === 0) return;
2405
+ const bytes = pendingFrameCreditBytes;
2406
+ pendingFrameCreditBytes = 0;
2407
+ postToRenderer({ type: "frame-credit", bytes });
2408
+ }
2409
+ function returnFrameCredit(bytes) {
2410
+ pendingFrameCreditBytes += bytes;
2411
+ if (pendingFrameCreditBytes >= FRAME_CREDIT_BATCH_BYTES) {
2412
+ flushFrameCredit();
2413
+ } else if (frameCreditTimer === void 0) {
2414
+ frameCreditTimer = setTimeout(flushFrameCredit, FRAME_CREDIT_MAX_DELAY_MS);
2415
+ }
2416
+ }
2417
+ function beginPerformanceMeasurement() {
2418
+ const now = performance.now();
2419
+ performanceMeasurement = {
2420
+ startedAt: now,
2421
+ lastActivityAt: now,
2422
+ lastFrameAt: /* @__PURE__ */ new Map(),
2423
+ dirtySince: /* @__PURE__ */ new Map(),
2424
+ frames: {
2425
+ received: 0,
2426
+ bytes: 0,
2427
+ full: 0,
2428
+ incremental: 0,
2429
+ stale: 0,
2430
+ resyncRequested: 0,
2431
+ rowsDecoded: 0,
2432
+ glyphDefinitions: 0
2433
+ },
2434
+ scheduling: { flushes: 0, renderCalls: 0, maximumDirtyPanes: 0, panesPerFlush: [] },
2435
+ renderer: {
2436
+ queueSubmits: 0,
2437
+ fullRenders: 0,
2438
+ partialRenders: 0,
2439
+ damagedRows: 0,
2440
+ geometryCacheHits: 0,
2441
+ geometryCacheMisses: 0,
2442
+ canvasPixelFrames: 0,
2443
+ renderPasses: 0,
2444
+ drawCalls: 0,
2445
+ rectangleVertices: 0,
2446
+ monoGlyphVertices: 0,
2447
+ colorGlyphVertices: 0,
2448
+ fallbackGlyphVertices: 0,
2449
+ vertexUploadBytes: 0,
2450
+ atlasUploadBytes: 0,
2451
+ atlasUploadCalls: 0
2452
+ },
2453
+ samples: { frameApplyMs: [], renderCpuMs: [], dirtyToRenderMs: [], frameArrivalToRenderMs: [] }
2454
+ };
2455
+ renderer?.setPerformanceMeasurementEnabled?.(true);
2456
+ }
2457
+ function notePerformanceActivity(now = performance.now()) {
2458
+ if (performanceMeasurement) performanceMeasurement.lastActivityAt = now;
2459
+ }
2460
+ function recordRenderMetrics(metrics) {
2461
+ const target = performanceMeasurement?.renderer;
2462
+ if (!target) return;
2463
+ target.queueSubmits += metrics.queueSubmits;
2464
+ target.fullRenders += metrics.fullRenders;
2465
+ target.partialRenders += metrics.partialRenders;
2466
+ target.damagedRows += metrics.damagedRows;
2467
+ target.geometryCacheHits += metrics.geometryCacheHits;
2468
+ target.geometryCacheMisses += metrics.geometryCacheMisses;
2469
+ target.canvasPixelFrames += metrics.canvasPixels;
2470
+ target.renderPasses += metrics.renderPasses;
2471
+ target.drawCalls += metrics.drawCalls;
2472
+ target.rectangleVertices += metrics.rectangleVertices;
2473
+ target.monoGlyphVertices += metrics.monoGlyphVertices;
2474
+ target.colorGlyphVertices += metrics.colorGlyphVertices;
2475
+ target.fallbackGlyphVertices += metrics.fallbackGlyphVertices;
2476
+ target.vertexUploadBytes += metrics.vertexUploadBytes;
2477
+ target.atlasUploadBytes += metrics.atlasUploadBytes;
2478
+ target.atlasUploadCalls += metrics.atlasUploadCalls;
2479
+ }
2480
+ async function finishPerformanceMeasurement(requestId, quietMs, timeoutMs) {
2481
+ const active = performanceMeasurement;
2482
+ if (!active) throw new Error("No terminal render performance measurement is active");
2483
+ const deadline = performance.now() + timeoutMs;
2484
+ let timedOutWaitingForIdle = false;
2485
+ let gpuQueueDrainMs = null;
2486
+ while (true) {
2487
+ const now = performance.now();
2488
+ const hasVisibleDirty = [...dirty].some((id) => !occluded.has(id));
2489
+ if (!flushScheduled && !hasVisibleDirty && now - active.lastActivityAt >= quietMs) {
2490
+ const beforeDrainActivity = active.lastActivityAt;
2491
+ if (renderer?.settle) {
2492
+ const drainStarted = performance.now();
2493
+ await renderer.settle();
2494
+ gpuQueueDrainMs = performance.now() - drainStarted;
2495
+ }
2496
+ if (active.lastActivityAt === beforeDrainActivity && !flushScheduled) break;
2497
+ }
2498
+ if (now >= deadline) {
2499
+ timedOutWaitingForIdle = true;
2500
+ break;
2501
+ }
2502
+ await new Promise((resolve) => setTimeout(resolve, Math.min(25, Math.max(1, quietMs / 4))));
2503
+ }
2504
+ if (timedOutWaitingForIdle && renderer?.settle) {
2505
+ const drainStarted = performance.now();
2506
+ await renderer.settle();
2507
+ gpuQueueDrainMs = performance.now() - drainStarted;
2508
+ }
2509
+ const snapshot2 = {
2510
+ backend: renderer?.kind ?? "starting",
2511
+ durationMs: performance.now() - active.startedAt,
2512
+ timedOutWaitingForIdle,
2513
+ gpuQueueDrainMs,
2514
+ frames: active.frames,
2515
+ scheduling: active.scheduling,
2516
+ renderer: active.renderer,
2517
+ samples: active.samples
2518
+ };
2519
+ renderer?.setPerformanceMeasurementEnabled?.(false);
2520
+ if (performanceMeasurement === active) performanceMeasurement = void 0;
2521
+ postToRenderer({ type: "performance-result", requestId, snapshot: snapshot2 });
2522
+ }
2523
+ function postToRenderer(message) {
2524
+ self.postMessage(message);
2525
+ }
2526
+ function snapshot(sessionHandle) {
2527
+ let value = snapshots.get(sessionHandle);
2528
+ if (!value) {
2529
+ value = {
2530
+ rows: [],
2531
+ nativeRows: [],
2532
+ nativeStyleRows: [],
2533
+ glyphDefinitions: /* @__PURE__ */ new Map(),
2534
+ glyphPixelBytes: 0,
2535
+ styleDefinitions: /* @__PURE__ */ new Map(),
2536
+ rowRevisions: [],
2537
+ cursor: hiddenCursor,
2538
+ layoutEpoch: 0n,
2539
+ sessionEpoch: 0n,
2540
+ sequence: 0n,
2541
+ awaitingResync: false,
2542
+ catalogFallback: false,
2543
+ scrollbar: null
2544
+ };
2545
+ snapshots.set(sessionHandle, value);
2546
+ }
2547
+ return value;
2548
+ }
2549
+ function clearSessionCatalog(session) {
2550
+ for (const glyphId of session.glyphDefinitions.keys()) {
2551
+ const shared = sharedGlyphDefinitions.get(glyphId);
2552
+ if (!shared) continue;
2553
+ shared.references -= 1;
2554
+ if (shared.references <= 0) {
2555
+ sharedGlyphDefinitions.delete(glyphId);
2556
+ sharedGlyphPixelBytes -= shared.definition.pixels.byteLength;
2557
+ }
2558
+ }
2559
+ session.glyphDefinitions.clear();
2560
+ session.glyphPixelBytes = 0;
2561
+ retainedStyleDefinitions -= session.styleDefinitions.size;
2562
+ session.styleDefinitions.clear();
2563
+ }
2564
+ function installGlyphDefinitions(session, definitions) {
2565
+ for (const decoded of definitions) {
2566
+ if (session.glyphDefinitions.has(decoded.id)) continue;
2567
+ let shared = sharedGlyphDefinitions.get(decoded.id);
2568
+ const pixelBytes = shared?.definition.pixels.byteLength ?? decoded.pixels.byteLength;
2569
+ if (shared) {
2570
+ shared.references += 1;
2571
+ } else {
2572
+ shared = { definition: decoded, references: 1 };
2573
+ sharedGlyphDefinitions.set(decoded.id, shared);
2574
+ sharedGlyphPixelBytes += decoded.pixels.byteLength;
2575
+ }
2576
+ session.glyphDefinitions.set(decoded.id, shared.definition);
2577
+ session.glyphPixelBytes += pixelBytes;
2578
+ }
2579
+ }
2580
+ function installStyleDefinitions(session, definitions) {
2581
+ for (const definition of definitions) {
2582
+ if (session.styleDefinitions.has(definition.id)) continue;
2583
+ session.styleDefinitions.set(definition.id, definition);
2584
+ retainedStyleDefinitions += 1;
2585
+ }
2586
+ }
2587
+ function dropSessionSnapshot(sessionHandle) {
2588
+ const session = snapshots.get(sessionHandle);
2589
+ if (session) clearSessionCatalog(session);
2590
+ snapshots.delete(sessionHandle);
2591
+ }
2592
+ function defaults(sessionHandle) {
2593
+ let value = presentationDefaults.get(sessionHandle);
2594
+ if (!value) {
2595
+ value = { theme: DEFAULT_THEME, selection: null, visible: true };
2596
+ presentationDefaults.set(sessionHandle, value);
2597
+ }
2598
+ return value;
2599
+ }
2600
+ function surface(surfaceId, sessionHandle) {
2601
+ const existing = surfaces.get(surfaceId);
2602
+ if (existing) {
2603
+ if (existing.sessionHandle !== sessionHandle) throw new Error(`Surface ${surfaceId} changed sessions`);
2604
+ return existing;
2605
+ }
2606
+ const initial = defaults(sessionHandle);
2607
+ const value = {
2608
+ sessionHandle,
2609
+ focused: false,
2610
+ cursorBlinkVisible: true,
2611
+ selection: initial.selection,
2612
+ theme: initial.theme,
2613
+ damage: { full: true, rows: /* @__PURE__ */ new Set(), geometryChanged: true }
2614
+ };
2615
+ surfaces.set(surfaceId, value);
2616
+ let sessionSurfaceIds = surfaceIdsBySession.get(sessionHandle);
2617
+ if (!sessionSurfaceIds) {
2618
+ sessionSurfaceIds = /* @__PURE__ */ new Set();
2619
+ surfaceIdsBySession.set(sessionHandle, sessionSurfaceIds);
2620
+ }
2621
+ sessionSurfaceIds.add(surfaceId);
2622
+ if (!initial.visible) occluded.add(surfaceId);
2623
+ return value;
2624
+ }
2625
+ function surfaceIdsForSession(sessionHandle) {
2626
+ return surfaceIdsBySession.get(sessionHandle) ?? noSurfaceIds;
2627
+ }
2628
+ function deleteSurface(surfaceId) {
2629
+ const value = surfaces.get(surfaceId);
2630
+ if (!value) return void 0;
2631
+ surfaces.delete(surfaceId);
2632
+ const sessionSurfaceIds = surfaceIdsBySession.get(value.sessionHandle);
2633
+ sessionSurfaceIds?.delete(surfaceId);
2634
+ if (sessionSurfaceIds?.size === 0) surfaceIdsBySession.delete(value.sessionHandle);
2635
+ return value;
2636
+ }
2637
+ function renderView(session, presentation) {
2638
+ return {
2639
+ rows: session.rows,
2640
+ nativeRows: session.nativeRows,
2641
+ nativeStyleRows: session.nativeStyleRows,
2642
+ rowRevisions: session.rowRevisions,
2643
+ glyphDefinitions: session.glyphDefinitions,
2644
+ styleDefinitions: session.styleDefinitions,
2645
+ sessionEpoch: session.sessionEpoch,
2646
+ layoutEpoch: session.layoutEpoch,
2647
+ cursor: session.cursor,
2648
+ focused: presentation.focused,
2649
+ cursorBlinkVisible: presentation.cursorBlinkVisible,
2650
+ selection: presentation.selection,
2651
+ theme: presentation.theme,
2652
+ damage: presentation.damage
2653
+ };
2654
+ }
2655
+ function scheduleCursorBlink(surfaceId, reset) {
2656
+ const existing = cursorBlinkTimers.get(surfaceId);
2657
+ if (existing !== void 0) clearTimeout(existing);
2658
+ cursorBlinkTimers.delete(surfaceId);
2659
+ const value = surfaces.get(surfaceId);
2660
+ if (!value) return;
2661
+ const session = snapshot(value.sessionHandle);
2662
+ if (reset) value.cursorBlinkVisible = true;
2663
+ if (occluded.has(surfaceId) || !value.focused || !session.cursor.visible || !session.cursor.blinking) return;
2664
+ cursorBlinkTimers.set(
2665
+ surfaceId,
2666
+ setTimeout(() => {
2667
+ cursorBlinkTimers.delete(surfaceId);
2668
+ const current = surfaces.get(surfaceId);
2669
+ const currentSession = current ? snapshots.get(current.sessionHandle) : void 0;
2670
+ if (occluded.has(surfaceId) || !current || !currentSession || !current.focused || !currentSession.cursor.visible || !currentSession.cursor.blinking)
2671
+ return;
2672
+ current.cursorBlinkVisible = !current.cursorBlinkVisible;
2673
+ invalidateRows(surfaceId, [currentSession.cursor.y]);
2674
+ scheduleCursorBlink(surfaceId, false);
2675
+ }, CURSOR_BLINK_INTERVAL_MS)
2676
+ );
2677
+ }
2678
+ async function createRenderer() {
2679
+ if (forceCanvasFallback) {
2680
+ const canvas = new CanvasTerminalRenderer();
2681
+ postToRenderer({ type: "renderer-status", backend: canvas.kind, recovered: true });
2682
+ return canvas;
2683
+ }
2684
+ try {
2685
+ const gpu = await WebGpuTerminalRenderer.create((info) => void recoverDevice(info));
2686
+ recoveryAttempts = 0;
2687
+ postToRenderer({ type: "renderer-status", backend: gpu.kind });
2688
+ return gpu;
2689
+ } catch (error) {
2690
+ console.warn("[terminal-renderer] WebGPU unavailable; using diagnostic Canvas2D", error);
2691
+ const canvas = new CanvasTerminalRenderer();
2692
+ postToRenderer({ type: "renderer-status", backend: canvas.kind });
2693
+ return canvas;
2694
+ }
2695
+ }
2696
+ async function ensureRenderer() {
2697
+ rendererPromise ??= createRenderer();
2698
+ renderer = await rendererPromise;
2699
+ return renderer;
2700
+ }
2701
+ async function recoverDevice(info) {
2702
+ if (recovering || renderer?.kind === "canvas2d") return;
2703
+ recovering = true;
2704
+ recoveryAttempts += 1;
2705
+ console.warn(`[terminal-renderer] WebGPU device lost (${info.reason}): ${info.message}`);
2706
+ renderer = void 0;
2707
+ rendererPromise = WebGpuTerminalRenderer.create((nextInfo) => void recoverDevice(nextInfo));
2708
+ try {
2709
+ const next = await rendererPromise;
2710
+ renderer = next;
2711
+ recoveryAttempts = 0;
2712
+ recovering = false;
2713
+ for (const [id, mount2] of mounts) {
2714
+ next.mount(id, mount2.canvas);
2715
+ next.resize(id, mount2.size);
2716
+ invalidateFull(id);
2717
+ }
2718
+ postToRenderer({ type: "renderer-status", backend: next.kind, recovered: true });
2719
+ } catch (error) {
2720
+ console.error("[terminal-renderer] WebGPU recovery failed", error);
2721
+ rendererPromise = void 0;
2722
+ recovering = false;
2723
+ if (recoveryAttempts >= 3) {
2724
+ postToRenderer({ type: "renderer-reload-required", reason: "webgpu-device-loss" });
2725
+ return;
2726
+ }
2727
+ const delay = Math.min(5e3, 250 * 2 ** Math.min(recoveryAttempts, 4));
2728
+ setTimeout(() => void recoverDevice(info), delay);
2729
+ }
2730
+ }
2731
+ function scheduleFlush() {
2732
+ if (flushScheduled) return;
2733
+ flushScheduled = true;
2734
+ const run = () => {
2735
+ flushScheduled = false;
2736
+ void flush();
2737
+ };
2738
+ if (typeof self.requestAnimationFrame === "function") self.requestAnimationFrame(run);
2739
+ else setTimeout(run, 8);
2740
+ }
2741
+ async function flush() {
2742
+ const backend = await ensureRenderer();
2743
+ const ids = [...dirty].filter((id) => !occluded.has(id));
2744
+ if (performanceMeasurement && ids.length > 0) {
2745
+ performanceMeasurement.scheduling.flushes += 1;
2746
+ appendPerformanceSample(performanceMeasurement.scheduling.panesPerFlush, ids.length);
2747
+ performanceMeasurement.scheduling.maximumDirtyPanes = Math.max(
2748
+ performanceMeasurement.scheduling.maximumDirtyPanes,
2749
+ ids.length
2750
+ );
2751
+ }
2752
+ for (const id of ids) dirty.delete(id);
2753
+ const entries = ids.flatMap((id) => {
2754
+ const presentation = surfaces.get(id);
2755
+ const session = presentation ? snapshots.get(presentation.sessionHandle) : void 0;
2756
+ return mounts.has(id) && presentation && session ? [{ id, view: renderView(session, presentation) }] : [];
2757
+ });
2758
+ if (entries.length > 0) {
2759
+ try {
2760
+ const active = performanceMeasurement;
2761
+ const beforeRender = active ? performance.now() : 0;
2762
+ const metrics = backend.renderBatch ? backend.renderBatch(entries) : entries.map(({ id, view }) => backend.render(id, view));
2763
+ const renderedAt = active ? performance.now() : 0;
2764
+ for (const { id } of entries) {
2765
+ const damage = surfaces.get(id)?.damage;
2766
+ if (!damage) continue;
2767
+ damage.full = false;
2768
+ damage.rows.clear();
2769
+ damage.geometryChanged = false;
2770
+ }
2771
+ if (active && performanceMeasurement === active) {
2772
+ active.scheduling.renderCalls += entries.length;
2773
+ appendPerformanceSample(active.samples.renderCpuMs, renderedAt - beforeRender);
2774
+ for (let index = 0; index < entries.length; index += 1) {
2775
+ const { id } = entries[index];
2776
+ const dirtySince = active.dirtySince.get(id);
2777
+ const sessionHandle = surfaces.get(id)?.sessionHandle;
2778
+ const lastFrameAt = sessionHandle ? active.lastFrameAt.get(sessionHandle) : void 0;
2779
+ if (dirtySince !== void 0)
2780
+ appendPerformanceSample(active.samples.dirtyToRenderMs, renderedAt - dirtySince);
2781
+ if (lastFrameAt !== void 0)
2782
+ appendPerformanceSample(active.samples.frameArrivalToRenderMs, renderedAt - lastFrameAt);
2783
+ active.dirtySince.delete(id);
2784
+ recordRenderMetrics(metrics[index] ?? emptyRenderMetrics());
2785
+ }
2786
+ notePerformanceActivity(renderedAt);
2787
+ }
2788
+ } catch (error) {
2789
+ console.error(`[terminal-renderer] failed to render ${entries.length} view(s)`, error);
2790
+ }
2791
+ }
2792
+ if ([...dirty].some((id) => !occluded.has(id))) scheduleFlush();
2793
+ }
2794
+ function markDirty(id) {
2795
+ if (performanceMeasurement && !performanceMeasurement.dirtySince.has(id)) {
2796
+ performanceMeasurement.dirtySince.set(id, performance.now());
2797
+ }
2798
+ dirty.add(id);
2799
+ if (!occluded.has(id)) scheduleFlush();
2800
+ }
2801
+ function invalidateFull(id) {
2802
+ const value = surfaces.get(id);
2803
+ if (!value) return;
2804
+ const damage = value.damage;
2805
+ damage.full = true;
2806
+ damage.rows.clear();
2807
+ damage.geometryChanged = true;
2808
+ markDirty(id);
2809
+ }
2810
+ function invalidateRows(id, rows, geometryChanged = false) {
2811
+ if (!partialRenderingEnabled) {
2812
+ invalidateFull(id);
2813
+ return;
2814
+ }
2815
+ const value = surfaces.get(id);
2816
+ if (!value) return;
2817
+ const damage = value.damage;
2818
+ if (!damage.full) {
2819
+ damage.geometryChanged ||= geometryChanged;
2820
+ for (const row of rows) {
2821
+ if (Number.isSafeInteger(row) && row >= 0) damage.rows.add(row);
2822
+ }
2823
+ }
2824
+ markDirty(id);
2825
+ }
2826
+ function invalidateSessionFull(sessionHandle) {
2827
+ for (const surfaceId of surfaceIdsForSession(sessionHandle)) invalidateFull(surfaceId);
2828
+ }
2829
+ function invalidateSessionRows(sessionHandle, rows, geometryChanged = false) {
2830
+ const damagedRows = [...rows];
2831
+ for (const surfaceId of surfaceIdsForSession(sessionHandle)) invalidateRows(surfaceId, damagedRows, geometryChanged);
2832
+ }
2833
+ async function mount(surfaceId, sessionHandle, canvas) {
2834
+ if (mounts.has(surfaceId)) renderer?.unmount(surfaceId);
2835
+ snapshot(sessionHandle);
2836
+ surface(surfaceId, sessionHandle);
2837
+ const entry = { canvas, size: { width: 1, height: 1, dpr: 1 }, sessionHandle };
2838
+ mounts.set(surfaceId, entry);
2839
+ const backend = await ensureRenderer();
2840
+ if (mounts.get(surfaceId) !== entry) return;
2841
+ backend.mount(surfaceId, canvas);
2842
+ backend.resize(surfaceId, entry.size);
2843
+ invalidateFull(surfaceId);
2844
+ }
2845
+ function applyFrame(packet) {
2846
+ const active = performanceMeasurement;
2847
+ const applyStarted = active ? performance.now() : 0;
2848
+ if (active) {
2849
+ active.frames.received += 1;
2850
+ active.frames.bytes += packet.byteLength;
2851
+ active.lastActivityAt = applyStarted;
2852
+ }
2853
+ const frame = decodeFrame(packet);
2854
+ const id = frame.sessionHandle.toString();
2855
+ if (active) {
2856
+ active.lastFrameAt.set(id, applyStarted);
2857
+ if ((frame.flags & FrameFlag.FullSnapshot) !== 0) active.frames.full += 1;
2858
+ else active.frames.incremental += 1;
2859
+ }
2860
+ const previous = snapshot(id);
2861
+ const fullFrame = (frame.flags & FrameFlag.FullSnapshot) !== 0;
2862
+ const catalogReset = (frame.flags & FrameFlag.CatalogReset) !== 0;
2863
+ if (catalogReset && !fullFrame) {
2864
+ if (active) {
2865
+ active.frames.resyncRequested += 1;
2866
+ appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
2867
+ }
2868
+ previous.awaitingResync = true;
2869
+ postToRenderer({ type: "frame-resync-needed", sessionHandle: id });
2870
+ return;
2871
+ }
2872
+ const classification = classifyFrame(previous, {
2873
+ sessionEpoch: frame.sessionEpoch,
2874
+ layoutEpoch: frame.layoutEpoch,
2875
+ sequence: frame.frameSequence,
2876
+ full: fullFrame
2877
+ });
2878
+ if (classification === "stale") {
2879
+ if (active) {
2880
+ active.frames.stale += 1;
2881
+ appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
2882
+ }
2883
+ return;
2884
+ }
2885
+ const changedSession = previous.sessionEpoch !== 0n && frame.sessionEpoch !== previous.sessionEpoch;
2886
+ if (classification === "resync") {
2887
+ if (active) {
2888
+ active.frames.resyncRequested += 1;
2889
+ appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
2890
+ }
2891
+ previous.awaitingResync = true;
2892
+ postToRenderer({ type: "frame-resync-needed", sessionHandle: id });
2893
+ return;
2894
+ }
2895
+ const completingResync = previous.awaitingResync;
2896
+ const rowSection = frame.sections.find((candidate) => candidate.kind === SectionKind.RowReplacements);
2897
+ const cursorSection = frame.sections.find((candidate) => candidate.kind === SectionKind.CursorState);
2898
+ const glyphSection = frame.sections.find((candidate) => candidate.kind === SectionKind.GlyphDefinitions);
2899
+ const styleSection = frame.sections.find((candidate) => candidate.kind === SectionKind.StyleDefinitions);
2900
+ const clipboardSection = frame.sections.find((candidate) => candidate.kind === SectionKind.ClipboardWrite);
2901
+ const scrollbarSection = frame.sections.find((candidate) => candidate.kind === SectionKind.ScrollbarState);
2902
+ if (!rowSection || !cursorSection) {
2903
+ if (active) appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
2904
+ return;
2905
+ }
2906
+ const glyphDefinitions = glyphSection ? decodeGlyphDefinitions(glyphSection) : NO_GLYPH_DEFINITIONS;
2907
+ const styleDefinitions = styleSection ? decodeStyleDefinitions(styleSection) : NO_STYLE_DEFINITIONS;
2908
+ if (active) active.frames.glyphDefinitions += glyphDefinitions.length;
2909
+ const resetsCatalog = changedSession || completingResync || catalogReset;
2910
+ const wasCatalogFallback = previous.catalogFallback;
2911
+ if (resetsCatalog) {
2912
+ previous.rows = [];
2913
+ previous.nativeRows = [];
2914
+ previous.nativeStyleRows = [];
2915
+ clearSessionCatalog(previous);
2916
+ previous.rowRevisions = [];
2917
+ const catalogFits = glyphCatalogFits(
2918
+ previous.glyphDefinitions,
2919
+ previous.glyphPixelBytes,
2920
+ sharedGlyphDefinitions,
2921
+ sharedGlyphPixelBytes,
2922
+ glyphDefinitions,
2923
+ GLYPH_CATALOG_BUDGET
2924
+ ) && definitionCatalogFits(
2925
+ previous.styleDefinitions,
2926
+ styleDefinitions,
2927
+ MAX_SESSION_STYLE_DEFINITIONS,
2928
+ retainedStyleDefinitions,
2929
+ MAX_RETAINED_STYLE_DEFINITIONS
2930
+ );
2931
+ const admission = catalogAdmission(true, wasCatalogFallback, catalogFits);
2932
+ previous.catalogFallback = admission === "text-fallback";
2933
+ if (admission === "install") {
2934
+ installGlyphDefinitions(previous, glyphDefinitions);
2935
+ installStyleDefinitions(previous, styleDefinitions);
2936
+ } else if (!wasCatalogFallback) {
2937
+ postToRenderer({
2938
+ type: "catalog-pressure",
2939
+ sessionHandle: id,
2940
+ reason: "working-set-exceeds-budget"
2941
+ });
2942
+ }
2943
+ } else if (!previous.catalogFallback) {
2944
+ const catalogFits = glyphCatalogFits(
2945
+ previous.glyphDefinitions,
2946
+ previous.glyphPixelBytes,
2947
+ sharedGlyphDefinitions,
2948
+ sharedGlyphPixelBytes,
2949
+ glyphDefinitions,
2950
+ GLYPH_CATALOG_BUDGET
2951
+ ) && definitionCatalogFits(
2952
+ previous.styleDefinitions,
2953
+ styleDefinitions,
2954
+ MAX_SESSION_STYLE_DEFINITIONS,
2955
+ retainedStyleDefinitions,
2956
+ MAX_RETAINED_STYLE_DEFINITIONS
2957
+ );
2958
+ const admission = catalogAdmission(false, false, catalogFits);
2959
+ if (admission === "request-full") {
2960
+ if (active) {
2961
+ active.frames.resyncRequested += 1;
2962
+ appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
2963
+ }
2964
+ previous.awaitingResync = true;
2965
+ postToRenderer({ type: "frame-resync-needed", sessionHandle: id });
2966
+ return;
2967
+ }
2968
+ if (admission === "install") {
2969
+ installGlyphDefinitions(previous, glyphDefinitions);
2970
+ installStyleDefinitions(previous, styleDefinitions);
2971
+ }
2972
+ }
2973
+ if (!previous.catalogFallback && !nativeTextAnnounced && glyphDefinitions.length > 0) {
2974
+ nativeTextAnnounced = true;
2975
+ postToRenderer({ type: "renderer-status", backend: renderer?.kind ?? "starting", textEngine: "native" });
2976
+ }
2977
+ if (clipboardSection) {
2978
+ postToRenderer({ type: "clipboard-write", text: decodeClipboardWrite(clipboardSection) });
2979
+ }
2980
+ if (scrollbarSection) {
2981
+ const decoded = decodeScrollbarState(scrollbarSection);
2982
+ const scrollbar = {
2983
+ total: Number(decoded.total),
2984
+ offset: Number(decoded.offset),
2985
+ length: Number(decoded.length)
2986
+ };
2987
+ if (!Number.isSafeInteger(scrollbar.total) || !Number.isSafeInteger(scrollbar.offset) || !Number.isSafeInteger(scrollbar.length))
2988
+ throw new RangeError("Scrollbar state exceeds JavaScript's safe integer range");
2989
+ if (!previous.scrollbar || previous.scrollbar.total !== scrollbar.total || previous.scrollbar.offset !== scrollbar.offset || previous.scrollbar.length !== scrollbar.length) {
2990
+ previous.scrollbar = scrollbar;
2991
+ postToRenderer({ type: "scrollbar-state", sessionHandle: id, scrollbar });
2992
+ }
2993
+ }
2994
+ const full = (rowSection.flags & 1) !== 0;
2995
+ const useNativeCatalog = !previous.catalogFallback;
2996
+ const rows = full ? Array(frame.rows).fill("") : previous.rows.slice();
2997
+ const nativeRows = full ? Array.from({ length: frame.rows }, () => []) : previous.nativeRows.slice();
2998
+ const nativeStyleRows = full ? Array.from({ length: frame.rows }, () => []) : previous.nativeStyleRows.slice();
2999
+ const rowRevisions = full ? Array(frame.rows).fill(0n) : previous.rowRevisions.slice();
3000
+ const replacements = decodeRowReplacements(rowSection);
3001
+ const damagedRows = [];
3002
+ if (active) active.frames.rowsDecoded += replacements.length;
3003
+ for (const replacement of replacements) {
3004
+ if (replacement.row >= frame.rows) throw new RangeError("Row replacement exceeds viewport");
3005
+ if (replacement.revision < (rowRevisions[replacement.row] ?? 0n)) continue;
3006
+ rows[replacement.row] = replacement.text;
3007
+ nativeRows[replacement.row] = useNativeCatalog ? replacement.glyphs : [];
3008
+ nativeStyleRows[replacement.row] = useNativeCatalog ? replacement.styles : [];
3009
+ rowRevisions[replacement.row] = replacement.revision;
3010
+ damagedRows.push(replacement.row);
3011
+ }
3012
+ previous.rows = rows;
3013
+ previous.nativeRows = nativeRows;
3014
+ previous.nativeStyleRows = nativeStyleRows;
3015
+ previous.rowRevisions = rowRevisions;
3016
+ const geometryChanged = damagedRows.length > 0;
3017
+ const previousCursor = previous.cursor;
3018
+ const nextCursor = decodeCursorState(cursorSection);
3019
+ const cursorChanged = nextCursor.x !== previousCursor.x || nextCursor.y !== previousCursor.y || nextCursor.visible !== previousCursor.visible || nextCursor.style !== previousCursor.style || nextCursor.blinking !== previousCursor.blinking;
3020
+ previous.cursor = nextCursor;
3021
+ previous.layoutEpoch = frame.layoutEpoch;
3022
+ previous.sessionEpoch = frame.sessionEpoch;
3023
+ previous.sequence = frame.frameSequence;
3024
+ previous.awaitingResync = false;
3025
+ if (completingResync) postToRenderer({ type: "frame-resync-complete", sessionHandle: id });
3026
+ const requiresFullRedraw = full || changedSession || completingResync;
3027
+ if (!requiresFullRedraw && cursorChanged) damagedRows.push(previousCursor.y, nextCursor.y);
3028
+ const hasRowDamage = damagedRows.length > 0;
3029
+ for (const surfaceId of surfaceIdsForSession(id)) {
3030
+ if (cursorChanged) scheduleCursorBlink(surfaceId, true);
3031
+ if (requiresFullRedraw) invalidateFull(surfaceId);
3032
+ else if (hasRowDamage) invalidateRows(surfaceId, damagedRows, geometryChanged);
3033
+ }
3034
+ if (active) appendPerformanceSample(active.samples.frameApplyMs, performance.now() - applyStarted);
3035
+ }
3036
+ self.onmessage = (event) => {
3037
+ const message = event.data;
3038
+ try {
3039
+ if (message.type === "mount") {
3040
+ void mount(message.surfaceId, message.sessionHandle, message.canvas).catch((error) => {
3041
+ console.error("[terminal-renderer] mount failed", error);
3042
+ });
3043
+ } else if (message.type === "renderer-config") {
3044
+ forceCanvasFallback = Boolean(message.forceCanvasFallback);
3045
+ } else if (message.type === "partial-rendering") {
3046
+ partialRenderingEnabled = Boolean(message.enabled);
3047
+ for (const id of mounts.keys()) invalidateFull(id);
3048
+ } else if (message.type === "unmount") {
3049
+ mounts.delete(message.surfaceId);
3050
+ dirty.delete(message.surfaceId);
3051
+ deleteSurface(message.surfaceId);
3052
+ occluded.delete(message.surfaceId);
3053
+ renderer?.unmount(message.surfaceId);
3054
+ const timer = cursorBlinkTimers.get(message.surfaceId);
3055
+ if (timer !== void 0) clearTimeout(timer);
3056
+ cursorBlinkTimers.delete(message.surfaceId);
3057
+ } else if (message.type === "drop-session") {
3058
+ for (const surfaceId of surfaceIdsForSession(message.sessionHandle)) {
3059
+ mounts.delete(surfaceId);
3060
+ dirty.delete(surfaceId);
3061
+ surfaces.delete(surfaceId);
3062
+ occluded.delete(surfaceId);
3063
+ renderer?.unmount(surfaceId);
3064
+ const timer = cursorBlinkTimers.get(surfaceId);
3065
+ if (timer !== void 0) clearTimeout(timer);
3066
+ cursorBlinkTimers.delete(surfaceId);
3067
+ }
3068
+ surfaceIdsBySession.delete(message.sessionHandle);
3069
+ dropSessionSnapshot(message.sessionHandle);
3070
+ presentationDefaults.delete(message.sessionHandle);
3071
+ performanceMeasurement?.lastFrameAt.delete(message.sessionHandle);
3072
+ } else if (message.type === "resize") {
3073
+ const mounted = mounts.get(message.surfaceId);
3074
+ if (!mounted) return;
3075
+ const nextSize = { width: message.width, height: message.height, dpr: message.dpr };
3076
+ const changed = renderedSizeChanged(mounted.size, nextSize);
3077
+ mounted.size = nextSize;
3078
+ if (!changed) return;
3079
+ renderer?.resize(message.surfaceId, mounted.size);
3080
+ invalidateFull(message.surfaceId);
3081
+ } else if (message.type === "frame") {
3082
+ try {
3083
+ applyFrame(message.packet);
3084
+ } finally {
3085
+ returnFrameCredit(message.packet.byteLength);
3086
+ }
3087
+ } else if (message.type === "frame-gap") {
3088
+ for (const sessionHandle of message.sessionHandles) {
3089
+ const session = snapshot(sessionHandle);
3090
+ if (session.awaitingResync) continue;
3091
+ session.awaitingResync = true;
3092
+ postToRenderer({ type: "frame-resync-needed", sessionHandle });
3093
+ }
3094
+ } else if (message.type === "expect-full") {
3095
+ snapshot(message.sessionHandle).awaitingResync = true;
3096
+ } else if (message.type === "theme") {
3097
+ if (message.surfaceId) {
3098
+ surface(message.surfaceId, message.sessionHandle).theme = message.theme;
3099
+ invalidateFull(message.surfaceId);
3100
+ } else {
3101
+ defaults(message.sessionHandle).theme = message.theme;
3102
+ for (const surfaceId of surfaceIdsForSession(message.sessionHandle)) {
3103
+ surfaces.get(surfaceId).theme = message.theme;
3104
+ invalidateFull(surfaceId);
3105
+ }
3106
+ }
3107
+ } else if (message.type === "selection") {
3108
+ if (message.surfaceId) {
3109
+ surface(message.surfaceId, message.sessionHandle).selection = message.selection;
3110
+ invalidateFull(message.surfaceId);
3111
+ } else {
3112
+ defaults(message.sessionHandle).selection = message.selection;
3113
+ for (const surfaceId of surfaceIdsForSession(message.sessionHandle)) {
3114
+ surfaces.get(surfaceId).selection = message.selection;
3115
+ invalidateFull(surfaceId);
3116
+ }
3117
+ }
3118
+ } else if (message.type === "visibility") {
3119
+ if (!message.surfaceId) defaults(message.sessionHandle).visible = message.visible;
3120
+ if (message.surfaceId) surface(message.surfaceId, message.sessionHandle);
3121
+ const targetIds = message.surfaceId ? [message.surfaceId] : surfaceIdsForSession(message.sessionHandle);
3122
+ for (const surfaceId of targetIds) {
3123
+ if (message.visible) {
3124
+ occluded.delete(surfaceId);
3125
+ invalidateFull(surfaceId);
3126
+ scheduleCursorBlink(surfaceId, true);
3127
+ } else {
3128
+ occluded.add(surfaceId);
3129
+ const timer = cursorBlinkTimers.get(surfaceId);
3130
+ if (timer !== void 0) clearTimeout(timer);
3131
+ cursorBlinkTimers.delete(surfaceId);
3132
+ }
3133
+ }
3134
+ } else if (message.type === "focus") {
3135
+ const value = surface(message.surfaceId, message.sessionHandle);
3136
+ value.focused = Boolean(message.focused);
3137
+ scheduleCursorBlink(message.surfaceId, value.focused);
3138
+ invalidateRows(message.surfaceId, [snapshot(message.sessionHandle).cursor.y]);
3139
+ } else if (message.type === "cursor-activity") {
3140
+ const session = snapshot(message.sessionHandle);
3141
+ for (const surfaceId of surfaceIdsForSession(message.sessionHandle)) {
3142
+ const value = surfaces.get(surfaceId);
3143
+ const changesPixels = cursorActivityChangesPixels(session.cursor, value.focused, value.cursorBlinkVisible);
3144
+ scheduleCursorBlink(surfaceId, true);
3145
+ if (changesPixels) invalidateRows(surfaceId, [session.cursor.y]);
3146
+ }
3147
+ } else if (message.type === "force-full-redraw") {
3148
+ invalidateSessionFull(message.sessionHandle);
3149
+ } else if (message.type === "force-row-redraw") {
3150
+ invalidateSessionRows(message.sessionHandle, [message.row]);
3151
+ } else if (message.type === "performance-start") {
3152
+ void ensureRenderer().then(() => {
3153
+ beginPerformanceMeasurement();
3154
+ postToRenderer({ type: "performance-started", requestId: message.requestId });
3155
+ }).catch((error) => console.error("[terminal-renderer] failed to start performance measurement", error));
3156
+ } else if (message.type === "performance-finish") {
3157
+ void finishPerformanceMeasurement(message.requestId, message.quietMs, message.timeoutMs).catch((error) => {
3158
+ console.error("[terminal-renderer] failed to finish performance measurement", error);
3159
+ });
3160
+ }
3161
+ } catch (error) {
3162
+ console.error("[terminal-renderer] rejected worker message", error);
3163
+ }
3164
+ };
3165
+ //# sourceMappingURL=terminal-render.worker.js.map