@aurigax-ai/ghostty-web 0.3.8 → 0.3.9

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.
package/lib/ghostty.ts CHANGED
@@ -232,8 +232,8 @@ export interface TrackedRow {
232
232
  const POINT_ACTIVE = 'ACTIVE';
233
233
  const POINT_HISTORY = 'HISTORY';
234
234
  const POINT_SCREEN = 'SCREEN';
235
- type PointSpace = typeof POINT_ACTIVE | typeof POINT_HISTORY;
236
- type PointTag = PointSpace | typeof POINT_SCREEN;
235
+ type PointSpace = typeof POINT_ACTIVE | typeof POINT_HISTORY | typeof POINT_SCREEN;
236
+ type PointTag = PointSpace;
237
237
 
238
238
  const GRAPHEME_CAP = 16;
239
239
  const GRAPHEME_CLUSTER_MODE = 2027;
@@ -1037,6 +1037,61 @@ export class GhosttyTerminal {
1037
1037
  return out;
1038
1038
  }
1039
1039
 
1040
+ /**
1041
+ * The rows `from` to `to` (0 is the oldest scrollback row) of the screen that
1042
+ * is showing, as text with its colours and styles as escape sequences. A
1043
+ * wrapped line comes back as one line, lines end in CR LF, and empty rows at
1044
+ * the end are dropped. One call into the engine, whatever the number of rows.
1045
+ */
1046
+ formatRows(from: number, to: number): string {
1047
+ const abi = this.abi;
1048
+ const optionsSize = abi.sizeOf('GhosttyFormatterTerminalOptions');
1049
+ const selectionSize = abi.sizeOf('GhosttySelection');
1050
+ const refSize = abi.sizeOf('GhosttyGridRef');
1051
+ const lastCol = this._cols - 1;
1052
+ return abi.with(optionsSize + selectionSize, (base) => {
1053
+ const options = base;
1054
+ const selection = base + optionsSize;
1055
+ const O = (field: string) => options + abi.offset('GhosttyFormatterTerminalOptions', field);
1056
+ const S = (field: string) => selection + abi.offset('GhosttySelection', field);
1057
+ abi.view().setUint32(selection, selectionSize, true);
1058
+ const place = (field: string, x: number, y: number) =>
1059
+ this.gridRef(POINT_SCREEN, x, y, (ref) => {
1060
+ abi.bytes().copyWithin(S(field), ref, ref + refSize);
1061
+ return true;
1062
+ });
1063
+ if (!place('start', 0, from) || !place('end', lastCol, to)) return '';
1064
+ const view = abi.view();
1065
+ view.setUint32(O('size'), optionsSize, true);
1066
+ view.setInt32(O('emit'), abi.enumValue('GhosttyFormatterFormat', 'VT'), true);
1067
+ view.setUint8(O('unwrap'), 1);
1068
+ view.setUint8(O('trim'), 1);
1069
+ view.setUint32(O('extra'), abi.sizeOf('GhosttyFormatterTerminalExtra'), true);
1070
+ view.setUint32(O('selection'), selection, true);
1071
+ const slot = abi.call('ghostty_wasm_alloc_opaque');
1072
+ try {
1073
+ abi.check('ghostty_formatter_terminal_new', 0, slot, this.handle, options);
1074
+ const formatter = abi.call('ghostty_wasm_take_opaque', slot);
1075
+ try {
1076
+ const written = abi.scratch(4);
1077
+ const probe = abi.call('ghostty_formatter_format_buf', formatter, 0, 0, written);
1078
+ if (probe === GHOSTTY_SUCCESS) return '';
1079
+ if (probe !== GHOSTTY_OUT_OF_SPACE)
1080
+ throw new GhosttyCallError('ghostty_formatter_format_buf', probe);
1081
+ const need = abi.view().getUint32(written, true);
1082
+ return abi.with(need, (buf) => {
1083
+ abi.check('ghostty_formatter_format_buf', formatter, buf, need, written);
1084
+ return abi.string(buf, abi.view().getUint32(written, true));
1085
+ });
1086
+ } finally {
1087
+ abi.call('ghostty_formatter_free', formatter);
1088
+ }
1089
+ } finally {
1090
+ abi.call('ghostty_wasm_free_opaque', slot);
1091
+ }
1092
+ });
1093
+ }
1094
+
1040
1095
  getGrapheme(row: number, col: number): number[] | null {
1041
1096
  return this.gridRef(POINT_ACTIVE, col, row, (ref) => this.graphemesAt(ref)) ?? null;
1042
1097
  }
package/lib/headless.ts CHANGED
@@ -150,6 +150,35 @@ export class HeadlessTerminal implements IDisposable {
150
150
  this.refreshMarkers(engine.rowsDiscarded());
151
151
  }
152
152
 
153
+ /**
154
+ * The logical lines of the rows `from` to `to` (0 is the oldest scrollback
155
+ * row) of the screen that is showing, each with its colours and styles as
156
+ * escape sequences. A wrapped line is one line, so the count is the number
157
+ * of rows that do not continue the row before them, empty rows at the end
158
+ * included. Costs one call into the engine, not one per row or cell.
159
+ */
160
+ serialize(range: { from: number; to: number }): string[] {
161
+ const engine = this.wasmTerm;
162
+ if (!engine || range.to < range.from) return [];
163
+ const text = engine.formatRows(range.from, range.to);
164
+ const printed = text === '' ? [] : text.split('\r\n');
165
+ const scrollback = engine.isAlternateScreen() ? 0 : engine.getScrollbackLength();
166
+ const wrapped = (y: number): boolean =>
167
+ y < scrollback ? engine.isScrollbackRowWrapped(y) : engine.isRowWrapped(y - scrollback);
168
+ const starts = [range.from];
169
+ for (let y = range.from + 1; y <= range.to; y++) if (!wrapped(y)) starts.push(y);
170
+ const lines: string[] = [];
171
+ let at = 0;
172
+ for (const [i, start] of starts.entries()) {
173
+ const rows = (starts[i + 1] ?? range.to + 1) - start;
174
+ let blank = 0;
175
+ while (blank < rows - 1 && engine.formatRows(start + blank, start + blank) === '') blank++;
176
+ at += blank;
177
+ lines.push(' '.repeat(blank * this.cols) + (printed[at++] ?? ''));
178
+ }
179
+ return lines;
180
+ }
181
+
153
182
  registerMarker(cursorYOffset = 0): IMarker | undefined {
154
183
  const engine = this.wasmTerm;
155
184
  if (!engine) return undefined;
@@ -850,6 +850,7 @@ export class InputHandler {
850
850
  private handleMouseDown(event: MouseEvent): void {
851
851
  if (this.isDisposed) return;
852
852
  if (!this.mouseConfig?.hasMouseTracking()) return;
853
+ if (event.shiftKey) return;
853
854
 
854
855
  const cell = this.pixelToCell(event);
855
856
  if (!cell) return;
@@ -875,6 +876,7 @@ export class InputHandler {
875
876
  private handleMouseUp(event: MouseEvent): void {
876
877
  if (this.isDisposed) return;
877
878
  if (!this.mouseConfig?.hasMouseTracking()) return;
879
+ if (event.shiftKey) return;
878
880
 
879
881
  const cell = this.pixelToCell(event);
880
882
  if (!cell) return;
@@ -895,6 +897,7 @@ export class InputHandler {
895
897
  private handleMouseMove(event: MouseEvent): void {
896
898
  if (this.isDisposed) return;
897
899
  if (!this.mouseConfig?.hasMouseTracking()) return;
900
+ if (event.shiftKey) return;
898
901
 
899
902
  // Check if button motion mode or any-event tracking is enabled
900
903
  // Mode 1002 = button motion, Mode 1003 = any motion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurigax-ai/ghostty-web",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Ghostty's terminal engine (libghostty-vt) in the browser: WebAssembly build, an xterm.js-shaped API and WebGL and canvas renderers. A hard fork of coder/ghostty-web.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.ts",