wraithterm 0.0.0-stage → 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 (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +313 -2
  3. package/css/xterm.css +285 -0
  4. package/dist/index.d.ts +88 -0
  5. package/dist/index.js +707 -0
  6. package/dist/keycodes.d.ts +1 -0
  7. package/dist/keycodes.js +179 -0
  8. package/dist/protocol.d.ts +117 -0
  9. package/dist/protocol.js +1 -0
  10. package/dist/types.d.ts +66 -0
  11. package/dist/types.js +19 -0
  12. package/dist/vite.d.ts +11 -0
  13. package/dist/vite.js +11 -0
  14. package/dist/worker.d.ts +1 -0
  15. package/dist/worker.js +302 -0
  16. package/dist/xterm-api.d.ts +1956 -0
  17. package/dist/xterm-atlas.d.ts +39 -0
  18. package/dist/xterm-atlas.js +176 -0
  19. package/dist/xterm-internals.d.ts +120 -0
  20. package/dist/xterm-internals.js +19 -0
  21. package/dist/xterm-renderer.d.ts +77 -0
  22. package/dist/xterm-renderer.js +540 -0
  23. package/dist/xterm.d.ts +17 -0
  24. package/dist/xterm.js +35 -0
  25. package/docs/ARCHITECTURE.md +179 -0
  26. package/docs/COMPATIBILITY.md +66 -0
  27. package/docs/PERFORMANCE.md +122 -0
  28. package/docs/TOP_TIER.md +124 -0
  29. package/docs/XTERM_API.md +163 -0
  30. package/docs/compatibility/xterm-chrome.json +34 -0
  31. package/licenses/Ghostty-MIT.txt +21 -0
  32. package/licenses/JetBrainsMono-OFL.txt +93 -0
  33. package/licenses/NOTICE.md +93 -0
  34. package/licenses/Zig-MIT.txt +21 -0
  35. package/licenses/rust/arrayvec-0.7.8/LICENSE-APACHE +201 -0
  36. package/licenses/rust/arrayvec-0.7.8/LICENSE-MIT +25 -0
  37. package/licenses/rust/autocfg-1.5.1/LICENSE-APACHE +201 -0
  38. package/licenses/rust/autocfg-1.5.1/LICENSE-MIT +25 -0
  39. package/licenses/rust/bit-set-0.10.0/LICENSE-APACHE +201 -0
  40. package/licenses/rust/bit-set-0.10.0/LICENSE-MIT +25 -0
  41. package/licenses/rust/bit-vec-0.9.1/LICENSE-APACHE +201 -0
  42. package/licenses/rust/bit-vec-0.9.1/LICENSE-MIT +25 -0
  43. package/licenses/rust/bitflags-2.13.2/LICENSE-APACHE +201 -0
  44. package/licenses/rust/bitflags-2.13.2/LICENSE-MIT +25 -0
  45. package/licenses/rust/bumpalo-3.20.3/LICENSE-APACHE +201 -0
  46. package/licenses/rust/bumpalo-3.20.3/LICENSE-MIT +25 -0
  47. package/licenses/rust/bytemuck-1.25.2/LICENSE-APACHE +61 -0
  48. package/licenses/rust/bytemuck-1.25.2/LICENSE-MIT +9 -0
  49. package/licenses/rust/bytemuck-1.25.2/LICENSE-ZLIB +11 -0
  50. package/licenses/rust/bytemuck_derive-1.12.1/LICENSE-APACHE +61 -0
  51. package/licenses/rust/bytemuck_derive-1.12.1/LICENSE-MIT +9 -0
  52. package/licenses/rust/bytemuck_derive-1.12.1/LICENSE-ZLIB +11 -0
  53. package/licenses/rust/cfg-if-1.0.5/LICENSE-APACHE +201 -0
  54. package/licenses/rust/cfg-if-1.0.5/LICENSE-MIT +25 -0
  55. package/licenses/rust/cfg_aliases-0.2.2/LICENSE +9 -0
  56. package/licenses/rust/codespan-reporting-0.13.1/LICENSE +201 -0
  57. package/licenses/rust/crunchy-0.2.4/LICENSE +21 -0
  58. package/licenses/rust/document-features-0.2.12/LICENSE-APACHE +73 -0
  59. package/licenses/rust/document-features-0.2.12/LICENSE-MIT +19 -0
  60. package/licenses/rust/equivalent-1.0.2/LICENSE-APACHE +201 -0
  61. package/licenses/rust/equivalent-1.0.2/LICENSE-MIT +25 -0
  62. package/licenses/rust/foldhash-0.2.0/LICENSE +19 -0
  63. package/licenses/rust/font-types-0.12.6/LICENSE-APACHE +67 -0
  64. package/licenses/rust/font-types-0.12.6/LICENSE-MIT +25 -0
  65. package/licenses/rust/futures-core-0.3.34/LICENSE-APACHE +202 -0
  66. package/licenses/rust/futures-core-0.3.34/LICENSE-MIT +26 -0
  67. package/licenses/rust/futures-task-0.3.34/LICENSE-APACHE +202 -0
  68. package/licenses/rust/futures-task-0.3.34/LICENSE-MIT +26 -0
  69. package/licenses/rust/futures-util-0.3.34/LICENSE-APACHE +202 -0
  70. package/licenses/rust/futures-util-0.3.34/LICENSE-MIT +26 -0
  71. package/licenses/rust/half-2.7.1/LICENSE-APACHE +176 -0
  72. package/licenses/rust/half-2.7.1/LICENSE-MIT +19 -0
  73. package/licenses/rust/harfrust-0.13.3/LICENSE +22 -0
  74. package/licenses/rust/hashbrown-0.17.1/LICENSE-APACHE +201 -0
  75. package/licenses/rust/hashbrown-0.17.1/LICENSE-MIT +25 -0
  76. package/licenses/rust/indexmap-2.14.2/LICENSE-APACHE +201 -0
  77. package/licenses/rust/indexmap-2.14.2/LICENSE-MIT +25 -0
  78. package/licenses/rust/js-sys-0.3.106/LICENSE-APACHE +201 -0
  79. package/licenses/rust/js-sys-0.3.106/LICENSE-MIT +25 -0
  80. package/licenses/rust/libc-0.2.190/LICENSE-APACHE +176 -0
  81. package/licenses/rust/libc-0.2.190/LICENSE-MIT +25 -0
  82. package/licenses/rust/libloading-0.8.9/LICENSE +12 -0
  83. package/licenses/rust/libm-0.2.16/LICENSE.txt +258 -0
  84. package/licenses/rust/litrs-1.0.0/LICENSE-APACHE +176 -0
  85. package/licenses/rust/litrs-1.0.0/LICENSE-MIT +25 -0
  86. package/licenses/rust/lock_api-0.4.14/LICENSE-APACHE +201 -0
  87. package/licenses/rust/lock_api-0.4.14/LICENSE-MIT +25 -0
  88. package/licenses/rust/log-0.4.34/LICENSE-APACHE +201 -0
  89. package/licenses/rust/log-0.4.34/LICENSE-MIT +25 -0
  90. package/licenses/rust/naga-30.0.1/LICENSE.APACHE +176 -0
  91. package/licenses/rust/naga-30.0.1/LICENSE.MIT +21 -0
  92. package/licenses/rust/naga-types-30.0.1/LICENSE.APACHE +176 -0
  93. package/licenses/rust/naga-types-30.0.1/LICENSE.MIT +21 -0
  94. package/licenses/rust/num-traits-0.2.19/LICENSE-APACHE +201 -0
  95. package/licenses/rust/num-traits-0.2.19/LICENSE-MIT +25 -0
  96. package/licenses/rust/once_cell-1.21.4/LICENSE-APACHE +201 -0
  97. package/licenses/rust/once_cell-1.21.4/LICENSE-MIT +23 -0
  98. package/licenses/rust/parking_lot-0.12.5/LICENSE-APACHE +201 -0
  99. package/licenses/rust/parking_lot-0.12.5/LICENSE-MIT +25 -0
  100. package/licenses/rust/parking_lot_core-0.9.12/LICENSE-APACHE +201 -0
  101. package/licenses/rust/parking_lot_core-0.9.12/LICENSE-MIT +25 -0
  102. package/licenses/rust/pin-project-lite-0.2.17/LICENSE-APACHE +177 -0
  103. package/licenses/rust/pin-project-lite-0.2.17/LICENSE-MIT +23 -0
  104. package/licenses/rust/portable-atomic-1.15.0/LICENSE-APACHE +177 -0
  105. package/licenses/rust/portable-atomic-1.15.0/LICENSE-MIT +23 -0
  106. package/licenses/rust/portable-atomic-util-0.2.8/LICENSE-APACHE +177 -0
  107. package/licenses/rust/portable-atomic-util-0.2.8/LICENSE-MIT +23 -0
  108. package/licenses/rust/proc-macro2-1.0.107/LICENSE-APACHE +176 -0
  109. package/licenses/rust/proc-macro2-1.0.107/LICENSE-MIT +23 -0
  110. package/licenses/rust/quote-1.0.47/LICENSE-APACHE +176 -0
  111. package/licenses/rust/quote-1.0.47/LICENSE-MIT +23 -0
  112. package/licenses/rust/raw-window-handle-0.6.2/LICENSE-APACHE.md +177 -0
  113. package/licenses/rust/raw-window-handle-0.6.2/LICENSE-MIT.md +21 -0
  114. package/licenses/rust/raw-window-handle-0.6.2/LICENSE-ZLIB.md +11 -0
  115. package/licenses/rust/read-fonts-0.41.0/LICENSE-APACHE +67 -0
  116. package/licenses/rust/read-fonts-0.41.0/LICENSE-MIT +25 -0
  117. package/licenses/rust/read-fonts-0.43.3/LICENSE-APACHE +67 -0
  118. package/licenses/rust/read-fonts-0.43.3/LICENSE-MIT +25 -0
  119. package/licenses/rust/redox_syscall-0.5.18/LICENSE +22 -0
  120. package/licenses/rust/renderdoc-sys-1.1.0/LICENSE-APACHE +201 -0
  121. package/licenses/rust/renderdoc-sys-1.1.0/LICENSE-MIT +25 -0
  122. package/licenses/rust/rustc-hash-1.1.0/LICENSE-APACHE +201 -0
  123. package/licenses/rust/rustc-hash-1.1.0/LICENSE-MIT +23 -0
  124. package/licenses/rust/rustversion-1.0.23/LICENSE-APACHE +176 -0
  125. package/licenses/rust/rustversion-1.0.23/LICENSE-MIT +23 -0
  126. package/licenses/rust/scopeguard-1.2.0/LICENSE-APACHE +201 -0
  127. package/licenses/rust/scopeguard-1.2.0/LICENSE-MIT +25 -0
  128. package/licenses/rust/serde-1.0.229/LICENSE-APACHE +176 -0
  129. package/licenses/rust/serde-1.0.229/LICENSE-MIT +23 -0
  130. package/licenses/rust/serde_core-1.0.229/LICENSE-APACHE +176 -0
  131. package/licenses/rust/serde_core-1.0.229/LICENSE-MIT +23 -0
  132. package/licenses/rust/serde_derive-1.0.229/LICENSE-APACHE +176 -0
  133. package/licenses/rust/serde_derive-1.0.229/LICENSE-MIT +23 -0
  134. package/licenses/rust/skrifa-0.44.0/LICENSE-APACHE +67 -0
  135. package/licenses/rust/skrifa-0.44.0/LICENSE-MIT +25 -0
  136. package/licenses/rust/slab-0.4.12/LICENSE +25 -0
  137. package/licenses/rust/smallvec-1.16.2/LICENSE-APACHE +201 -0
  138. package/licenses/rust/smallvec-1.16.2/LICENSE-MIT +25 -0
  139. package/licenses/rust/static_assertions-1.1.0/LICENSE-APACHE +202 -0
  140. package/licenses/rust/static_assertions-1.1.0/LICENSE-MIT +21 -0
  141. package/licenses/rust/swash-0.2.10/LICENSE-APACHE +201 -0
  142. package/licenses/rust/swash-0.2.10/LICENSE-MIT +25 -0
  143. package/licenses/rust/syn-2.0.119/LICENSE-APACHE +176 -0
  144. package/licenses/rust/syn-2.0.119/LICENSE-MIT +23 -0
  145. package/licenses/rust/syn-3.0.6/LICENSE-APACHE +176 -0
  146. package/licenses/rust/syn-3.0.6/LICENSE-MIT +23 -0
  147. package/licenses/rust/termcolor-1.4.1/COPYING +3 -0
  148. package/licenses/rust/termcolor-1.4.1/LICENSE-MIT +21 -0
  149. package/licenses/rust/termcolor-1.4.1/UNLICENSE +24 -0
  150. package/licenses/rust/thiserror-2.0.21/LICENSE-APACHE +176 -0
  151. package/licenses/rust/thiserror-2.0.21/LICENSE-MIT +23 -0
  152. package/licenses/rust/thiserror-impl-2.0.21/LICENSE-APACHE +176 -0
  153. package/licenses/rust/thiserror-impl-2.0.21/LICENSE-MIT +23 -0
  154. package/licenses/rust/tokio-1.53.2/LICENSE +21 -0
  155. package/licenses/rust/unicode-ident-1.0.26/LICENSE-APACHE +176 -0
  156. package/licenses/rust/unicode-ident-1.0.26/LICENSE-MIT +23 -0
  157. package/licenses/rust/unicode-ident-1.0.26/LICENSE-UNICODE +39 -0
  158. package/licenses/rust/unicode-width-0.2.2/LICENSE-APACHE +201 -0
  159. package/licenses/rust/unicode-width-0.2.2/LICENSE-MIT +25 -0
  160. package/licenses/rust/wasm-bindgen-0.2.129/LICENSE-APACHE +201 -0
  161. package/licenses/rust/wasm-bindgen-0.2.129/LICENSE-MIT +25 -0
  162. package/licenses/rust/wasm-bindgen-futures-0.4.79/LICENSE-APACHE +201 -0
  163. package/licenses/rust/wasm-bindgen-futures-0.4.79/LICENSE-MIT +25 -0
  164. package/licenses/rust/wasm-bindgen-macro-0.2.129/LICENSE-APACHE +201 -0
  165. package/licenses/rust/wasm-bindgen-macro-0.2.129/LICENSE-MIT +25 -0
  166. package/licenses/rust/wasm-bindgen-macro-support-0.2.129/LICENSE-APACHE +201 -0
  167. package/licenses/rust/wasm-bindgen-macro-support-0.2.129/LICENSE-MIT +25 -0
  168. package/licenses/rust/wasm-bindgen-shared-0.2.129/LICENSE-APACHE +201 -0
  169. package/licenses/rust/wasm-bindgen-shared-0.2.129/LICENSE-MIT +25 -0
  170. package/licenses/rust/web-sys-0.3.106/LICENSE-APACHE +201 -0
  171. package/licenses/rust/web-sys-0.3.106/LICENSE-MIT +25 -0
  172. package/licenses/rust/wgpu-30.0.1/LICENSE.APACHE +176 -0
  173. package/licenses/rust/wgpu-30.0.1/LICENSE.MIT +21 -0
  174. package/licenses/rust/wgpu-core-30.0.1/LICENSE.APACHE +176 -0
  175. package/licenses/rust/wgpu-core-30.0.1/LICENSE.MIT +21 -0
  176. package/licenses/rust/wgpu-core-deps-windows-linux-android-30.0.1/LICENSE.APACHE +176 -0
  177. package/licenses/rust/wgpu-core-deps-windows-linux-android-30.0.1/LICENSE.MIT +21 -0
  178. package/licenses/rust/wgpu-hal-30.0.1/LICENSE.APACHE +176 -0
  179. package/licenses/rust/wgpu-hal-30.0.1/LICENSE.MIT +21 -0
  180. package/licenses/rust/wgpu-naga-bridge-30.0.1/LICENSE.APACHE +176 -0
  181. package/licenses/rust/wgpu-naga-bridge-30.0.1/LICENSE.MIT +21 -0
  182. package/licenses/rust/wgpu-types-30.0.1/LICENSE.APACHE +176 -0
  183. package/licenses/rust/wgpu-types-30.0.1/LICENSE.MIT +21 -0
  184. package/licenses/rust/winapi-util-0.1.11/COPYING +3 -0
  185. package/licenses/rust/winapi-util-0.1.11/LICENSE-MIT +21 -0
  186. package/licenses/rust/winapi-util-0.1.11/UNLICENSE +24 -0
  187. package/licenses/rust/windows-link-0.2.1/license-apache-2.0 +201 -0
  188. package/licenses/rust/windows-link-0.2.1/license-mit +21 -0
  189. package/licenses/rust/windows-sys-0.61.2/license-apache-2.0 +201 -0
  190. package/licenses/rust/windows-sys-0.61.2/license-mit +21 -0
  191. package/licenses/rust/yazi-0.2.1/LICENSE-APACHE +201 -0
  192. package/licenses/rust/yazi-0.2.1/LICENSE-MIT +19 -0
  193. package/licenses/rust/zeno-0.3.3/LICENSE-APACHE +201 -0
  194. package/licenses/rust/zeno-0.3.3/LICENSE-MIT +25 -0
  195. package/licenses/rust/zerocopy-0.8.62/LICENSE-APACHE +202 -0
  196. package/licenses/rust/zerocopy-0.8.62/LICENSE-BSD +24 -0
  197. package/licenses/rust/zerocopy-0.8.62/LICENSE-MIT +26 -0
  198. package/licenses/rust/zerocopy-derive-0.8.62/LICENSE-APACHE +202 -0
  199. package/licenses/rust/zerocopy-derive-0.8.62/LICENSE-BSD +24 -0
  200. package/licenses/rust/zerocopy-derive-0.8.62/LICENSE-MIT +26 -0
  201. package/licenses/xterm-MIT.txt +21 -0
  202. package/package.json +69 -4
  203. package/wasm/Ghostty-MIT.txt +21 -0
  204. package/wasm/JetBrainsMono-Regular.ttf +0 -0
  205. package/wasm/OFL.txt +93 -0
  206. package/wasm/optimization.json +6 -0
  207. package/wasm/wraith_engine.d.ts +176 -0
  208. package/wasm/wraith_engine.js +2057 -0
  209. package/wasm/wraith_engine_bg.wasm +0 -0
  210. package/wasm/wraith_engine_bg.wasm.d.ts +71 -0
@@ -0,0 +1,540 @@
1
+ import init, { Compositor } from '../wasm/wraith_engine.js';
2
+ import { BrowserAtlas, AtlasFull } from './xterm-atlas.js';
3
+ import { internals, CELL_HAS_EXTENDED, CELL_IS_COMBINED, CELL_CODEPOINT_MASK, packedRow, } from './xterm-internals.js';
4
+ /** u32 words per packed GPU cell: fg, bg, underline, flags, glyph. */
5
+ export const CELL_WORDS = 5;
6
+ const rgb = (value) => ((value & 255) << 16) | (value & 0xff00) | ((value >>> 16) & 255);
7
+ const rgba = (value) => rgb(value >>> 8);
8
+ function wordColor(word, foreground, colors, brighten) {
9
+ const mode = word & 0x3000000;
10
+ if (mode === 0x3000000)
11
+ return rgb(word);
12
+ if (mode) {
13
+ const index = word & 255;
14
+ return rgba(colors.ansi[index + (brighten && index < 8 ? 8 : 0)].rgba);
15
+ }
16
+ return rgba((foreground ? colors.foreground : colors.background).rgba);
17
+ }
18
+ function resolveColor(data, foreground, bright, colors, boldBright) {
19
+ const color = foreground ? data.getFgColor() : data.getBgColor();
20
+ if (foreground ? data.isFgRGB() : data.isBgRGB())
21
+ return rgb(color);
22
+ if (foreground ? data.isFgPalette() : data.isBgPalette()) {
23
+ const index = color + (bright && data.isBold() && boldBright && color < 8 ? 8 : 0);
24
+ return rgba(colors.ansi[index].rgba);
25
+ }
26
+ return rgba((foreground ? colors.foreground : colors.background).rgba);
27
+ }
28
+ function sameRow(words, rows, offset, count) {
29
+ for (let i = 0; i < count; i++)
30
+ if (words[i] !== rows[offset + i])
31
+ return false;
32
+ return true;
33
+ }
34
+ let initialization;
35
+ /** Renderer replacement only: xterm keeps input, parser, scrollback and addons.
36
+ * A retained DOM renderer provides metrics and loss/feature fallback. */
37
+ export class XtermRenderer {
38
+ terminal;
39
+ core;
40
+ fallback;
41
+ state = 'initializing';
42
+ reason;
43
+ gpu;
44
+ memory;
45
+ atlas;
46
+ canvas;
47
+ rows;
48
+ subscriptions = [];
49
+ geometry = '';
50
+ font = '';
51
+ full = true;
52
+ staging = new Uint32Array(0);
53
+ /** Raw xterm words (3 per cell) that produced each staged row. Staging
54
+ * mirrors the GPU cell buffer, so a row whose words recur needs no packing:
55
+ * in place it is skipped, after scrolling it is copied within staging. */
56
+ rowWords = new Uint32Array(0);
57
+ /** 1 while staging row y is exactly the packing of rowWords row y. */
58
+ rowCached = new Uint8Array(0);
59
+ /** Frame stamp of the last write to each staging row this frame. */
60
+ rowStamp = new Uint32Array(0);
61
+ stamp = 0;
62
+ shift = 1;
63
+ lastViewportY = 0;
64
+ cell;
65
+ view = new Float32Array(4);
66
+ frame = new Uint32Array(8);
67
+ disposed = false;
68
+ fatal = false;
69
+ hovering = false;
70
+ blink = true;
71
+ timer;
72
+ selectedStart;
73
+ selectedEnd;
74
+ columnSelection = false;
75
+ ready;
76
+ get active() {
77
+ return this.core._renderService._renderer.value === this;
78
+ }
79
+ get dimensions() {
80
+ return this.fallback.dimensions;
81
+ }
82
+ get onRequestRedraw() {
83
+ return this.fallback.onRequestRedraw;
84
+ }
85
+ constructor(terminal, core, fallback) {
86
+ this.terminal = terminal;
87
+ this.core = core;
88
+ this.fallback = fallback;
89
+ const document = terminal.element.ownerDocument;
90
+ this.cell = terminal.buffer.active.getNullCell();
91
+ this.rows = core.screenElement.querySelector('.xterm-rows');
92
+ this.canvas = document.createElement('canvas');
93
+ this.canvas.className = 'wraith-xterm-webgpu';
94
+ this.canvas.setAttribute('aria-hidden', 'true');
95
+ Object.assign(this.canvas.style, {
96
+ position: 'absolute',
97
+ left: '0',
98
+ top: '0',
99
+ pointerEvents: 'none',
100
+ display: 'none',
101
+ });
102
+ core.screenElement.prepend(this.canvas);
103
+ this.subscriptions.push(core.optionsService.onOptionChange(() => this.invalidate()), core._themeService.onChangeColors(() => this.invalidate()), core.linkifier.onShowLinkUnderline(() => {
104
+ this.hovering = true;
105
+ this.invalidate();
106
+ }), core.linkifier.onHideLinkUnderline(() => {
107
+ this.hovering = false;
108
+ this.invalidate();
109
+ }));
110
+ this.timer = setInterval(() => {
111
+ if (!this.disposed &&
112
+ this.state === 'webgpu' &&
113
+ (core.coreService.decPrivateModes.cursorBlink ?? terminal.options.cursorBlink)) {
114
+ this.blink = !this.blink;
115
+ this.invalidate(false);
116
+ }
117
+ }, 600);
118
+ this.ready = this.initialize();
119
+ }
120
+ static install(terminal) {
121
+ const core = internals(terminal);
122
+ if (!core)
123
+ return undefined;
124
+ const fallback = core._renderService._renderer.clearAndLeak();
125
+ if (!fallback)
126
+ return undefined;
127
+ const renderer = new XtermRenderer(terminal, core, fallback);
128
+ core._renderService.setRenderer(renderer);
129
+ return renderer;
130
+ }
131
+ async initialize() {
132
+ try {
133
+ if (!navigator.gpu || !this.canvas.transferControlToOffscreen)
134
+ throw new Error('WebGPU or OffscreenCanvas is unavailable');
135
+ initialization ??= init();
136
+ const wasm = await initialization;
137
+ if (this.disposed)
138
+ return false;
139
+ const gpu = await Compositor.create(this.canvas.transferControlToOffscreen());
140
+ if (this.disposed) {
141
+ gpu.free();
142
+ return false;
143
+ }
144
+ this.gpu = gpu;
145
+ this.memory = wasm.memory;
146
+ this.state = 'webgpu';
147
+ this.invalidate();
148
+ return true;
149
+ }
150
+ catch (error) {
151
+ if (!this.disposed)
152
+ this.fail(String(error));
153
+ return false;
154
+ }
155
+ }
156
+ invalidate(full = true) {
157
+ this.full ||= full;
158
+ if (!this.disposed)
159
+ this.core._renderService.refreshRows(0, full ? this.terminal.rows - 1 : 0, true);
160
+ }
161
+ fail(reason) {
162
+ this.fatal = true;
163
+ this.reason = reason;
164
+ this.gpu?.free();
165
+ this.gpu = undefined;
166
+ this.atlas = undefined;
167
+ this.show(false);
168
+ }
169
+ show(gpu) {
170
+ this.state = gpu ? 'webgpu' : 'dom';
171
+ this.canvas.style.display = gpu ? 'block' : 'none';
172
+ if (this.rows)
173
+ this.rows.style.visibility = gpu ? 'hidden' : '';
174
+ // GPU selection colors are resolved per cell; avoid drawing the DOM
175
+ // selection tint twice. Accessibility lives outside this visual layer.
176
+ const selection = this.core.screenElement.querySelector('.xterm-selection');
177
+ if (selection)
178
+ selection.style.visibility = gpu ? 'hidden' : '';
179
+ }
180
+ fallbackReason() {
181
+ if (this.fatal)
182
+ return this.reason;
183
+ if (!this.gpu)
184
+ return 'GPU initialization';
185
+ if (this.terminal.options.allowTransparency)
186
+ return 'Transparency';
187
+ if (this.terminal.options.minimumContrastRatio !== 1)
188
+ return 'Minimum contrast adjustment';
189
+ if (this.core._characterJoinerService._characterJoiners.length)
190
+ return 'Character joiners';
191
+ if (!this.core._decorationService.decorations.next().done)
192
+ return 'Buffer decorations';
193
+ if (this.hovering)
194
+ return 'Link hover decorations';
195
+ return undefined;
196
+ }
197
+ renderRows(start, end) {
198
+ if (this.disposed)
199
+ return;
200
+ const reason = this.fallbackReason();
201
+ if (reason) {
202
+ if (this.state !== 'dom') {
203
+ this.full = true;
204
+ start = 0;
205
+ end = this.terminal.rows - 1;
206
+ }
207
+ this.reason = reason;
208
+ this.show(false);
209
+ this.fallback.renderRows(start, end);
210
+ return;
211
+ }
212
+ try {
213
+ const d = this.dimensions, o = this.terminal.options;
214
+ const geometry = `${this.terminal.cols},${this.terminal.rows},${d.device.canvas.width},${d.device.canvas.height}`;
215
+ if (!d.device.cell.width || !d.device.cell.height)
216
+ return;
217
+ if (geometry !== this.geometry) {
218
+ this.gpu.configure(this.terminal.cols, this.terminal.rows, Math.round(d.device.canvas.width), Math.round(d.device.canvas.height));
219
+ this.canvas.style.width = `${d.css.canvas.width}px`;
220
+ this.canvas.style.height = `${d.css.canvas.height}px`;
221
+ this.geometry = geometry;
222
+ this.full = true;
223
+ const rows = this.terminal.rows;
224
+ this.rowWords = new Uint32Array(rows * this.terminal.cols * 3);
225
+ this.rowCached = new Uint8Array(rows);
226
+ this.rowStamp = new Uint32Array(rows);
227
+ }
228
+ const font = `${o.fontFamily}|${o.fontSize}|${o.fontWeight}|${o.fontWeightBold}|${d.device.cell.width}|${d.device.cell.height}|${this.core._coreBrowserService.dpr}`;
229
+ if (font !== this.font) {
230
+ this.atlas = new BrowserAtlas(this.canvas.ownerDocument, this.gpu, o, this.core._coreBrowserService.dpr, d.device.cell.width, d.device.cell.height);
231
+ this.font = font;
232
+ this.full = true;
233
+ }
234
+ if (this.full || this.state !== 'webgpu') {
235
+ start = 0;
236
+ end = this.terminal.rows - 1;
237
+ this.rowCached.fill(0);
238
+ }
239
+ let changed;
240
+ try {
241
+ changed = this.pack(start, end);
242
+ }
243
+ catch (error) {
244
+ if (!(error instanceof AtlasFull))
245
+ throw error;
246
+ this.atlas.reset();
247
+ this.rowCached.fill(0);
248
+ changed = this.pack(0, this.terminal.rows - 1);
249
+ }
250
+ const b = this.terminal.buffer.active, colors = this.core._themeService.colors;
251
+ const cs = this.core.coreService;
252
+ const focused = this.core._coreBrowserService.isFocused;
253
+ const style = focused
254
+ ? (cs.decPrivateModes.cursorStyle ?? o.cursorStyle)
255
+ : o.cursorInactiveStyle;
256
+ const styles = { bar: 0, block: 1, underline: 2, outline: 3 };
257
+ const y = b.baseY + b.cursorY - b.viewportY;
258
+ const visible = cs.isCursorInitialized &&
259
+ !cs.isCursorHidden &&
260
+ style !== 'none' &&
261
+ y >= 0 &&
262
+ y < this.terminal.rows &&
263
+ (!focused || !(cs.decPrivateModes.cursorBlink ?? o.cursorBlink) || this.blink);
264
+ this.view[0] = d.device.cell.width;
265
+ this.view[1] = d.device.cell.height;
266
+ this.view[2] = this.atlas.width;
267
+ this.view[3] = this.atlas.height;
268
+ this.frame[0] = this.terminal.cols;
269
+ this.frame[1] = this.terminal.rows;
270
+ this.frame[2] = y * this.terminal.cols + Math.min(b.cursorX, this.terminal.cols - 1);
271
+ this.frame[3] = +visible | ((styles[style ?? 'outline'] ?? 3) << 1) | 65536;
272
+ this.frame[4] = rgba(colors.cursor.rgba);
273
+ this.frame[5] = rgba(colors.cursorAccent.rgba);
274
+ this.frame[7] =
275
+ 255 | (Math.max(1, Math.round(o.cursorWidth * this.core._coreBrowserService.dpr)) << 8);
276
+ const cols = this.terminal.cols;
277
+ const [first, last] = changed ?? [0, -1];
278
+ if (!this.gpu.render_staged((last - first + 1) * cols, first * cols)) {
279
+ this.invalidate();
280
+ return;
281
+ }
282
+ this.full = false;
283
+ this.reason = undefined;
284
+ this.show(true);
285
+ }
286
+ catch (error) {
287
+ this.fail(String(error));
288
+ this.fallback.renderRows(0, this.terminal.rows - 1);
289
+ }
290
+ }
291
+ refreshStaging() {
292
+ const pointer = this.gpu.staging_ptr;
293
+ const words = this.gpu.staging_words;
294
+ const buffer = this.memory.buffer;
295
+ if (this.staging.buffer !== buffer ||
296
+ this.staging.byteOffset !== pointer ||
297
+ this.staging.length !== words) {
298
+ this.staging = new Uint32Array(buffer, pointer, words);
299
+ this.view = new Float32Array(buffer, pointer + words * 4, 4);
300
+ this.frame = new Uint32Array(buffer, pointer + (words + 4) * 4, 8);
301
+ }
302
+ }
303
+ selected(x, y) {
304
+ const a = this.selectedStart, b = this.selectedEnd;
305
+ if (!a || !b)
306
+ return false;
307
+ if (this.columnSelection)
308
+ return (y >= Math.min(a[1], b[1]) &&
309
+ y <= Math.max(a[1], b[1]) &&
310
+ x >= Math.min(a[0], b[0]) &&
311
+ x < Math.max(a[0], b[0]));
312
+ return (y > a[1] || (y === a[1] && x >= a[0])) && (y < b[1] || (y === b[1] && x < b[0]));
313
+ }
314
+ /** Bring staging rows start..end up to date with the buffer. Returns the
315
+ * first and last rows whose staging changed, or undefined when none did. */
316
+ pack(start, end) {
317
+ const { cols, rows, buffer, options } = this.terminal;
318
+ const b = buffer.active, c = this.core._themeService.colors, atlas = this.atlas;
319
+ this.refreshStaging();
320
+ if (rows * cols * CELL_WORDS > this.staging.length || end >= rows)
321
+ throw new Error('Packed rows exceed configured staging');
322
+ let values = this.staging;
323
+ const rowWords = this.rowWords, cached = this.rowCached, stamps = this.rowStamp, stamp = ++this.stamp, raw = cols * 3, packed = cols * CELL_WORDS;
324
+ // Content moves up when the viewport advances, so copy from rows below
325
+ // first; when it moves back, walk upwards so copy sources stay intact.
326
+ const delta = b.viewportY - this.lastViewportY;
327
+ this.lastViewportY = b.viewportY;
328
+ const step = delta < 0 ? -1 : 1;
329
+ let first = rows, last = -1, searched = false;
330
+ const hasSelection = !!this.selectedStart && !!this.selectedEnd;
331
+ const selectionBackground = rgba((this.core._coreBrowserService.isFocused
332
+ ? c.selectionBackgroundOpaque
333
+ : c.selectionInactiveBackgroundOpaque).rgba);
334
+ const selectionForeground = c.selectionForeground
335
+ ? rgba(c.selectionForeground.rgba)
336
+ : undefined;
337
+ const brighten = !!options.drawBoldTextInBrightColors;
338
+ // Attribute runs are common even when every glyph differs. Keep just the
339
+ // previous decoded style, so RGB churn cannot grow or thrash a style cache.
340
+ // Words are unsigned, so -1 never matches a real attribute word.
341
+ let lastFg = -1, lastBg = -1, styleFg = 0, styleBg = 0, styleUnderline = 0, styleFlags = 0;
342
+ rowLoop: for (let y = step > 0 ? start : end; y >= start && y <= end; y += step) {
343
+ const row = b.viewportY + y;
344
+ const line = b.getLine(row);
345
+ const words = packedRow(line, cols);
346
+ const selectable = hasSelection && this.rowSelected(row);
347
+ if (words !== undefined && !selectable) {
348
+ if (cached[y] && sameRow(words, rowWords, y * raw, raw))
349
+ continue;
350
+ // Scrolled content: find this row's words among rows staged last
351
+ // frame that have not been overwritten yet, and move them.
352
+ const candidates = [this.shift, delta, 1];
353
+ for (let k = 0; k <= candidates.length; k++) {
354
+ let source = -1;
355
+ if (k < candidates.length) {
356
+ const s = y + candidates[k];
357
+ if (s !== y && s >= 0 && s < rows && cached[s] && stamps[s] !== stamp)
358
+ if (sameRow(words, rowWords, s * raw, raw))
359
+ source = s;
360
+ }
361
+ else if (!searched) {
362
+ searched = true;
363
+ for (let s = 0; s < rows && source < 0; s++)
364
+ if (s !== y &&
365
+ cached[s] &&
366
+ stamps[s] !== stamp &&
367
+ sameRow(words, rowWords, s * raw, raw))
368
+ source = s;
369
+ }
370
+ if (source < 0)
371
+ continue;
372
+ values.copyWithin(y * packed, source * packed, (source + 1) * packed);
373
+ rowWords.copyWithin(y * raw, source * raw, (source + 1) * raw);
374
+ cached[y] = 1;
375
+ stamps[y] = stamp;
376
+ this.shift = source - y;
377
+ first = Math.min(first, y);
378
+ last = Math.max(last, y);
379
+ continue rowLoop;
380
+ }
381
+ }
382
+ let direct = words !== undefined && !selectable;
383
+ for (let x = 0, i = y * packed; x < cols; x++, i += CELL_WORDS) {
384
+ if (words !== undefined) {
385
+ const content = words[x * 3], fgWord = words[x * 3 + 1], bgWord = words[x * 3 + 2];
386
+ // Ordinary scalar cells decode straight from xterm's row storage.
387
+ // Combined text, extended attributes and unknown widths use getters.
388
+ if (!(bgWord & CELL_HAS_EXTENDED) &&
389
+ !(content & CELL_IS_COMBINED) &&
390
+ content >>> 22 <= 2 &&
391
+ (content & CELL_CODEPOINT_MASK) <= 0x10ffff) {
392
+ if (fgWord !== lastFg || bgWord !== lastBg) {
393
+ const inverse = !!(fgWord & 0x4000000);
394
+ styleFg = wordColor(inverse ? bgWord : fgWord, !inverse, c, !!(fgWord & 0x8000000) && brighten);
395
+ styleBg = wordColor(inverse ? fgWord : bgWord, inverse, c, false);
396
+ styleUnderline = fgWord & 0x3000000 ? wordColor(fgWord, true, c, false) : -1;
397
+ styleFlags =
398
+ ((fgWord >>> 27) & 1) |
399
+ (((bgWord >>> 26) & 1) << 1) |
400
+ (((bgWord >>> 27) & 1) << 2) |
401
+ (((fgWord >>> 30) & 1) << 4) |
402
+ ((fgWord >>> 31) << 5) |
403
+ (((bgWord >>> 30) & 1) << 6) |
404
+ (((fgWord >>> 28) & 1) << 8);
405
+ lastFg = fgWord;
406
+ lastBg = bgWord;
407
+ }
408
+ let fg = styleFg, bg = styleBg;
409
+ if (selectable && this.selected(x, row)) {
410
+ bg = selectionBackground;
411
+ if (selectionForeground !== undefined)
412
+ fg = selectionForeground;
413
+ }
414
+ const width = content >>> 22;
415
+ values[i] = fg;
416
+ values[i + 1] = bg;
417
+ values[i + 2] = styleUnderline < 0 ? fg : styleUnderline;
418
+ values[i + 3] = width === 2 ? styleFlags | 65536 : styleFlags;
419
+ values[i + 4] = width ? atlas.scalar(content & CELL_CODEPOINT_MASK, styleFlags & 3) : 0;
420
+ continue;
421
+ }
422
+ }
423
+ lastFg = lastBg = -1;
424
+ direct = false;
425
+ const data = line?.getCell(x, this.cell);
426
+ if (!data) {
427
+ values[i] = rgba(c.foreground.rgba);
428
+ values[i + 1] = rgba(c.background.rgba);
429
+ values.fill(0, i + 2, i + CELL_WORDS);
430
+ continue;
431
+ }
432
+ let fg = resolveColor(data, !data.isInverse(), true, c, brighten), bg = resolveColor(data, !!data.isInverse(), false, c, false);
433
+ const flags = +!!data.isBold() |
434
+ (+!!data.isItalic() << 1) |
435
+ (+!!data.isDim() << 2) |
436
+ (+!!data.isInvisible() << 4) |
437
+ (+!!data.isStrikethrough() << 5) |
438
+ (+!!data.isOverline() << 6) |
439
+ (data.getUnderlineStyle() << 8);
440
+ if (selectable && this.selected(x, row)) {
441
+ bg = selectionBackground;
442
+ if (selectionForeground !== undefined)
443
+ fg = selectionForeground;
444
+ }
445
+ const width = data.getWidth();
446
+ const content = data.content;
447
+ values[i] = fg;
448
+ values[i + 1] = bg;
449
+ values[i + 2] = data.isUnderlineColorRGB()
450
+ ? rgb(data.getUnderlineColor())
451
+ : data.isUnderlineColorPalette()
452
+ ? rgba(c.ansi[data.getUnderlineColor()].rgba)
453
+ : fg;
454
+ values[i + 3] = width === 2 ? flags | 65536 : flags;
455
+ values[i + 4] = !width
456
+ ? 0
457
+ : typeof content === 'number' &&
458
+ !(content & CELL_IS_COMBINED) &&
459
+ (content & CELL_CODEPOINT_MASK) <= 0x10ffff
460
+ ? atlas.scalar(content & CELL_CODEPOINT_MASK, flags & 3)
461
+ : atlas.tile(data.getChars(), flags & 3);
462
+ }
463
+ if (values.buffer !== this.memory.buffer) {
464
+ // A cold glyph upload can grow Wasm memory mid-row and detach views.
465
+ // Earlier writes survive at the same addresses; repeat this row with
466
+ // fresh views to replace any writes skipped after detachment.
467
+ this.refreshStaging();
468
+ values = this.staging;
469
+ lastFg = lastBg = -1;
470
+ y -= step;
471
+ continue;
472
+ }
473
+ // Rows with combined text, extended attributes or selection depend on
474
+ // more than their words; they are always repacked.
475
+ if (direct)
476
+ rowWords.set(words.subarray(0, raw), y * raw);
477
+ cached[y] = +direct;
478
+ stamps[y] = stamp;
479
+ first = Math.min(first, y);
480
+ last = Math.max(last, y);
481
+ }
482
+ return last < 0 ? undefined : [first, last];
483
+ }
484
+ rowSelected(row) {
485
+ const a = this.selectedStart, b = this.selectedEnd;
486
+ return row >= Math.min(a[1], b[1]) && row <= Math.max(a[1], b[1]);
487
+ }
488
+ handleDevicePixelRatioChange() {
489
+ this.fallback.handleDevicePixelRatioChange();
490
+ this.invalidate();
491
+ }
492
+ handleResize(cols, rows) {
493
+ this.fallback.handleResize(cols, rows);
494
+ this.invalidate();
495
+ }
496
+ handleCharSizeChanged() {
497
+ this.fallback.handleCharSizeChanged();
498
+ this.invalidate();
499
+ }
500
+ handleBlur() {
501
+ this.fallback.handleBlur();
502
+ this.invalidate();
503
+ }
504
+ handleFocus() {
505
+ this.blink = true;
506
+ this.fallback.handleFocus();
507
+ this.invalidate();
508
+ }
509
+ handleSelectionChanged(start, end, column) {
510
+ this.selectedStart = start;
511
+ this.selectedEnd = end;
512
+ this.columnSelection = column;
513
+ this.fallback.handleSelectionChanged(start, end, column);
514
+ this.invalidate();
515
+ }
516
+ handleCursorMove() {
517
+ this.blink = true;
518
+ this.fallback.handleCursorMove();
519
+ this.invalidate(false);
520
+ }
521
+ clear() {
522
+ this.fallback.clear();
523
+ this.invalidate();
524
+ }
525
+ clearTextureAtlas() {
526
+ this.atlas?.reset();
527
+ this.invalidate();
528
+ }
529
+ dispose() {
530
+ if (this.disposed)
531
+ return;
532
+ this.disposed = true;
533
+ clearInterval(this.timer);
534
+ for (const subscription of this.subscriptions)
535
+ subscription.dispose();
536
+ this.gpu?.free();
537
+ this.canvas.remove();
538
+ this.fallback.dispose();
539
+ }
540
+ }
@@ -0,0 +1,17 @@
1
+ import type { Terminal as TerminalApi, ITerminalOptions, ITerminalInitOnlyOptions } from './xterm-api.js';
2
+ import { type RendererState } from './xterm-renderer.js';
3
+ declare const XtermTerminal: typeof TerminalApi;
4
+ export type * from './xterm-api.js';
5
+ /** Complete xterm.js 6.0 public API. Construction/open remain synchronous;
6
+ * WebGPU warms asynchronously while xterm's DOM renderer remains usable. */
7
+ export declare class Terminal extends XtermTerminal {
8
+ private wraithRenderer;
9
+ private wraithDisposed;
10
+ constructor(options?: ITerminalOptions & ITerminalInitOnlyOptions);
11
+ /** Resolves after GPU initialization. False means the DOM fallback is active. */
12
+ get webgpuReady(): Promise<boolean>;
13
+ get renderer(): RendererState;
14
+ get rendererFallbackReason(): string | undefined;
15
+ open(parent: HTMLElement): void;
16
+ dispose(): void;
17
+ }
package/dist/xterm.js ADDED
@@ -0,0 +1,35 @@
1
+ import { Terminal as RuntimeTerminal } from '@wraith-terminal/xterm-base/lib/xterm.mjs';
2
+ import { XtermRenderer } from './xterm-renderer.js';
3
+ const XtermTerminal = RuntimeTerminal;
4
+ /** Complete xterm.js 6.0 public API. Construction/open remain synchronous;
5
+ * WebGPU warms asynchronously while xterm's DOM renderer remains usable. */
6
+ export class Terminal extends XtermTerminal {
7
+ wraithRenderer;
8
+ wraithDisposed = false;
9
+ constructor(options) {
10
+ super(options);
11
+ }
12
+ /** Resolves after GPU initialization. False means the DOM fallback is active. */
13
+ get webgpuReady() {
14
+ return this.wraithRenderer?.ready ?? Promise.resolve(false);
15
+ }
16
+ get renderer() {
17
+ return this.wraithRenderer
18
+ ? this.wraithRenderer.active
19
+ ? this.wraithRenderer.state
20
+ : 'external'
21
+ : 'dom';
22
+ }
23
+ get rendererFallbackReason() {
24
+ return this.wraithRenderer?.reason;
25
+ }
26
+ open(parent) {
27
+ super.open(parent);
28
+ if (!this.wraithRenderer && !this.wraithDisposed)
29
+ this.wraithRenderer = XtermRenderer.install(this);
30
+ }
31
+ dispose() {
32
+ this.wraithDisposed = true;
33
+ super.dispose();
34
+ }
35
+ }