@alchemy.run/sigil 0.0.0-alpha.5 → 0.0.0-alpha.7
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/README.md +1 -1
- package/dist/{Text-D5HUf3Fj.d.ts → Text-DV9CuzAT.d.ts} +3 -3
- package/dist/ansi.d.ts +1 -1
- package/dist/ansi.js +2 -2
- package/dist/capabilities.d.ts +4 -4
- package/dist/capabilities.js +2 -2
- package/dist/{color-policy-BMzMwV7Q.d.ts → color-policy-CCxuHIdD.d.ts} +2 -2
- package/dist/{color-policy-SVj1pYTA.js → color-policy-DlrZXC0f.js} +31 -13
- package/dist/{color-profile-u0Nhe9Nv.d.ts → color-profile-CyeHnG1T.d.ts} +1 -1
- package/dist/color.d.ts +2 -2
- package/dist/{detect-BuTXtY6e.js → detect-B3dL4Q11.js} +3 -2
- package/dist/{detect-Bh4yGP6w.d.ts → detect-Db6GbKOm.d.ts} +9 -0
- package/dist/{index-Bmc2tRPk.d.ts → index-D48vQhhe.d.ts} +2 -2
- package/dist/index.d.ts +10 -6
- package/dist/index.js +85 -72
- package/dist/{osc-CCH7xDoS.js → osc-BFKKSqpg.js} +1 -1
- package/dist/{paint-C19minOS.d.ts → paint-Cx-zC_sX.d.ts} +2 -2
- package/dist/{query-vaIeGOkH.d.ts → query-BNc2B8GD.d.ts} +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.js +1 -1
- package/dist/{screen-BReKIheE.d.ts → screen-CgC2WlVM.d.ts} +12 -3
- package/dist/{screen-BOh__-65.js → screen-CiPytswf.js} +30 -12
- package/dist/screen.d.ts +3 -3
- package/dist/screen.js +1 -1
- package/dist/{session-BJmzX2NJ.js → session-Cg6STjFV.js} +45 -9
- package/dist/{store-CgrG9K4y.d.ts → store-C1P5fOUi.d.ts} +1 -1
- package/dist/terminal.d.ts +8 -5
- package/dist/terminal.js +1 -1
- package/dist/{use-focus-C2sciZOo.js → use-focus-DSV01Ulb.js} +3 -1
- package/package.json +1 -1
- package/src/capabilities/detect.ts +15 -1
- package/src/capabilities/store.ts +17 -2
- package/src/components/Text.tsx +1 -1
- package/src/dom.ts +6 -0
- package/src/hooks/use-window-size.ts +8 -19
- package/src/ink.tsx +93 -23
- package/src/paint-tree.ts +7 -3
- package/src/screen/canvas.ts +49 -26
- package/src/screen/screen.ts +45 -12
- package/src/structured-text.ts +4 -0
- package/src/styles.ts +7 -1
- package/src/terminal/screen-presenter.ts +62 -8
- package/src/terminal/session.ts +3 -2
- package/src/testing/terminal.ts +15 -3
- package/src/wrap-text.ts +4 -0
- package/src/utils.ts +0 -40
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as Cell, o as CellPatch } from "./color-profile-
|
|
1
|
+
import { i as Cell, o as CellPatch } from "./color-profile-CyeHnG1T.js";
|
|
2
2
|
import { n as Rect } from "./geometry-BxXOzJgo.js";
|
|
3
3
|
//#region src/screen/screen.d.ts
|
|
4
4
|
type Line = readonly Cell[];
|
|
@@ -16,17 +16,26 @@ declare class Screen {
|
|
|
16
16
|
#private;
|
|
17
17
|
constructor(width: number, height: number);
|
|
18
18
|
get width(): number;
|
|
19
|
+
/**
|
|
20
|
+
The populated length of one row: the nominal width, or more when overflow
|
|
21
|
+
writes (`textWrap: "none"` content) extended it past the screen's width.
|
|
22
|
+
*/
|
|
23
|
+
rowLength(y: number): number;
|
|
19
24
|
get height(): number;
|
|
20
25
|
bounds(): Rect;
|
|
21
26
|
cellAt(x: number, y: number): Cell | undefined;
|
|
22
27
|
/** Whether a drawable explicitly touched this position. */
|
|
23
28
|
isPainted(x: number, y: number): boolean;
|
|
24
|
-
setCell(x: number, y: number, cell: Cell
|
|
29
|
+
setCell(x: number, y: number, cell: Cell, options?: {
|
|
30
|
+
overflow?: boolean;
|
|
31
|
+
}): void;
|
|
25
32
|
/**
|
|
26
33
|
Composes independent cell channels over the existing cell. An undefined
|
|
27
34
|
patch is transparent; an explicit space in `content` paints a blank.
|
|
28
35
|
*/
|
|
29
|
-
composeCell(x: number, y: number, patch: CellPatch | undefined
|
|
36
|
+
composeCell(x: number, y: number, patch: CellPatch | undefined, options?: {
|
|
37
|
+
overflow?: boolean;
|
|
38
|
+
}): void;
|
|
30
39
|
/** Composes a patch throughout a rectangle, clipped to this screen. */
|
|
31
40
|
fill(bounds: Rect, patch: CellPatch | undefined): void;
|
|
32
41
|
/** Resizes while preserving complete graphemes in the shared top-left area. */
|
|
@@ -155,6 +155,13 @@ var Screen = class {
|
|
|
155
155
|
get width() {
|
|
156
156
|
return this.#width;
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
The populated length of one row: the nominal width, or more when overflow
|
|
160
|
+
writes (`textWrap: "none"` content) extended it past the screen's width.
|
|
161
|
+
*/
|
|
162
|
+
rowLength(y) {
|
|
163
|
+
return Math.max(this.#width, this.#rows[y]?.length ?? 0);
|
|
164
|
+
}
|
|
158
165
|
get height() {
|
|
159
166
|
return this.#height;
|
|
160
167
|
}
|
|
@@ -173,17 +180,20 @@ var Screen = class {
|
|
|
173
180
|
isPainted(x, y) {
|
|
174
181
|
return this.#painted[y]?.[x] ?? false;
|
|
175
182
|
}
|
|
176
|
-
setCell(x, y, cell) {
|
|
183
|
+
setCell(x, y, cell, options) {
|
|
177
184
|
const row = this.#rows[y];
|
|
178
|
-
|
|
185
|
+
const overflow = options?.overflow === true;
|
|
186
|
+
if (!row || x < 0 || !overflow && x >= this.width) return;
|
|
179
187
|
if (!Number.isInteger(cell.width) || cell.width < 1) throw new Error("Only leading cells with a positive width may be written");
|
|
180
188
|
const finalColumn = x + cell.width;
|
|
181
|
-
|
|
182
|
-
const
|
|
189
|
+
if (overflow && finalColumn > row.length) this.#growRow(row, y, finalColumn);
|
|
190
|
+
const capacity = overflow ? row.length : this.width;
|
|
191
|
+
const clipped = finalColumn > capacity;
|
|
192
|
+
const overwriteEnd = Math.min(finalColumn, capacity);
|
|
183
193
|
for (let column = x; column < overwriteEnd; column++) this.#clearGraphemeAt(row, y, column);
|
|
184
194
|
if (clipped) {
|
|
185
|
-
for (let column = x; column <
|
|
186
|
-
this.#markDirty(y, x,
|
|
195
|
+
for (let column = x; column < capacity; column++) row[column] = emptyCell;
|
|
196
|
+
this.#markDirty(y, x, capacity);
|
|
187
197
|
return;
|
|
188
198
|
}
|
|
189
199
|
row[x] = cell;
|
|
@@ -203,10 +213,10 @@ var Screen = class {
|
|
|
203
213
|
Composes independent cell channels over the existing cell. An undefined
|
|
204
214
|
patch is transparent; an explicit space in `content` paints a blank.
|
|
205
215
|
*/
|
|
206
|
-
composeCell(x, y, patch) {
|
|
216
|
+
composeCell(x, y, patch, options) {
|
|
207
217
|
if (!patch) return;
|
|
208
218
|
const row = this.#rows[y];
|
|
209
|
-
if (!row || x < 0 || x >= this.width) return;
|
|
219
|
+
if (!row || x < 0 || options?.overflow !== true && x >= this.width) return;
|
|
210
220
|
let leadingColumn = x;
|
|
211
221
|
while (leadingColumn > 0 && row[leadingColumn]?.width === 0) leadingColumn--;
|
|
212
222
|
const destination = row[leadingColumn] ?? emptyCell;
|
|
@@ -220,7 +230,7 @@ var Screen = class {
|
|
|
220
230
|
};
|
|
221
231
|
const content = patch.content ?? destination;
|
|
222
232
|
const hyperlink = patch.hyperlink === void 0 ? destination.hyperlink : patch.hyperlink ?? void 0;
|
|
223
|
-
this.setCell(writeColumn, y, createCell(content.grapheme, content.width, style, hyperlink));
|
|
233
|
+
this.setCell(writeColumn, y, createCell(content.grapheme, content.width, style, hyperlink), options);
|
|
224
234
|
}
|
|
225
235
|
/** Composes a patch throughout a rectangle, clipped to this screen. */
|
|
226
236
|
fill(bounds, patch) {
|
|
@@ -265,7 +275,7 @@ var Screen = class {
|
|
|
265
275
|
for (const [y, row] of this.#rows.entries()) {
|
|
266
276
|
row.fill(emptyCell);
|
|
267
277
|
this.#painted[y].fill(true);
|
|
268
|
-
this.#markDirty(y, 0, this.
|
|
278
|
+
this.#markDirty(y, 0, this.rowLength(y));
|
|
269
279
|
}
|
|
270
280
|
}
|
|
271
281
|
toRows() {
|
|
@@ -282,6 +292,14 @@ var Screen = class {
|
|
|
282
292
|
this.#dirty.fill(void 0);
|
|
283
293
|
return spans;
|
|
284
294
|
}
|
|
295
|
+
/** Extends a row (and its paint mask) so overflow writes land past `width`. */
|
|
296
|
+
#growRow(row, y, length) {
|
|
297
|
+
const painted = this.#painted[y];
|
|
298
|
+
while (row.length < length) {
|
|
299
|
+
row.push(emptyCell);
|
|
300
|
+
painted.push(false);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
285
303
|
#clearGraphemeAt(row, y, x) {
|
|
286
304
|
const existing = row[x];
|
|
287
305
|
if (!existing || existing === emptyCell) return;
|
|
@@ -289,7 +307,7 @@ var Screen = class {
|
|
|
289
307
|
while (leadingColumn > 0 && row[leadingColumn]?.width === 0) leadingColumn--;
|
|
290
308
|
const leadingCell = row[leadingColumn];
|
|
291
309
|
const width = Math.max(1, leadingCell?.width ?? 1);
|
|
292
|
-
for (let column = leadingColumn; column < Math.min(leadingColumn + width,
|
|
310
|
+
for (let column = leadingColumn; column < Math.min(leadingColumn + width, row.length); column++) {
|
|
293
311
|
row[column] = emptyCell;
|
|
294
312
|
this.#painted[y][column] = true;
|
|
295
313
|
}
|
|
@@ -306,7 +324,7 @@ var Screen = class {
|
|
|
306
324
|
#markDirty(y, start, end) {
|
|
307
325
|
if (y < 0 || y >= this.height || start >= end) return;
|
|
308
326
|
const clippedStart = Math.max(0, start);
|
|
309
|
-
const clippedEnd = Math.min(this.
|
|
327
|
+
const clippedEnd = Math.min(this.rowLength(y), end);
|
|
310
328
|
const current = this.#dirty[y];
|
|
311
329
|
this.#dirty[y] = current ? {
|
|
312
330
|
start: Math.min(current.start, clippedStart),
|
package/dist/screen.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as emptyCellStyle, a as CellContent, c as Color, d as RgbColor, f as UnderlineStyle, g as emptyCell, h as createCell, i as Cell, l as Hyperlink, m as cellsEqual, n as colorProfileFromLevel, o as CellPatch, p as cellAttributes, r as quantizeColor, s as CellStyle, t as ColorProfile, u as IndexedColor } from "./color-profile-
|
|
1
|
+
import { _ as emptyCellStyle, a as CellContent, c as Color, d as RgbColor, f as UnderlineStyle, g as emptyCell, h as createCell, i as Cell, l as Hyperlink, m as cellsEqual, n as colorProfileFromLevel, o as CellPatch, p as cellAttributes, r as quantizeColor, s as CellStyle, t as ColorProfile, u as IndexedColor } from "./color-profile-CyeHnG1T.js";
|
|
2
2
|
import { n as Rect, t as Point } from "./geometry-BxXOzJgo.js";
|
|
3
|
-
import { a as cellFromStyledChar, i as styledCharFromCell, n as serializeLine, o as cellsFromAnsi, r as serializeScreen, t as SerializeOptions } from "./index-
|
|
4
|
-
import { n as Screen, t as Line } from "./screen-
|
|
3
|
+
import { a as cellFromStyledChar, i as styledCharFromCell, n as serializeLine, o as cellsFromAnsi, r as serializeScreen, t as SerializeOptions } from "./index-D48vQhhe.js";
|
|
4
|
+
import { n as Screen, t as Line } from "./screen-CgC2WlVM.js";
|
|
5
5
|
export { Cell, CellContent, CellPatch, CellStyle, Color, ColorProfile, Hyperlink, IndexedColor, type Line, Point, Rect, RgbColor, Screen, SerializeOptions, UnderlineStyle, cellAttributes, cellFromStyledChar, cellsEqual, cellsFromAnsi, colorProfileFromLevel, createCell, emptyCell, emptyCellStyle, quantizeColor, serializeLine, serializeScreen, styledCharFromCell };
|
package/dist/screen.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as emptyCellStyle, i as emptyCell, n as cellsEqual, r as createCell, t as cellAttributes } from "./cell-_ZVhbfl0.js";
|
|
2
|
-
import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-
|
|
2
|
+
import { n as cellFromStyledChar, r as cellsFromAnsi, t as Screen } from "./screen-CiPytswf.js";
|
|
3
3
|
import { n as quantizeColor, t as colorProfileFromLevel } from "./color-profile-DHhQHY55.js";
|
|
4
4
|
import { n as serializeScreen, r as styledCharFromCell, t as serializeLine } from "./serialize-BTkAZgw1.js";
|
|
5
5
|
export { Screen, cellAttributes, cellFromStyledChar, cellsEqual, cellsFromAnsi, colorProfileFromLevel, createCell, emptyCell, emptyCellStyle, quantizeColor, serializeLine, serializeScreen, styledCharFromCell };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { G as pasteEnd, K as pasteStart, S as ansiEscapes } from "./sgr-BhwaWAJB.js";
|
|
2
|
-
import { a as setPointerShape, c as setWorkingDirectory, i as setClipboard, l as tmuxPassthrough, n as notify, o as setTerminalProgress, s as setWindowTitle, u as cliCursor } from "./osc-
|
|
3
|
-
import { i as getCapabilities, t as colorState } from "./color-policy-
|
|
4
|
-
import { n as cellsEqual } from "./cell-_ZVhbfl0.js";
|
|
2
|
+
import { a as setPointerShape, c as setWorkingDirectory, i as setClipboard, l as tmuxPassthrough, n as notify, o as setTerminalProgress, s as setWindowTitle, u as cliCursor } from "./osc-BFKKSqpg.js";
|
|
3
|
+
import { i as getCapabilities, t as colorState } from "./color-policy-DlrZXC0f.js";
|
|
4
|
+
import { n as cellsEqual, t as cellAttributes } from "./cell-_ZVhbfl0.js";
|
|
5
5
|
import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
|
|
6
6
|
//#region src/cursor-position.ts
|
|
7
7
|
const showCursorEscape = ansiEscapes.cursorShow;
|
|
@@ -322,13 +322,29 @@ var ScreenPresenter = class {
|
|
|
322
322
|
willPresent(screen, cursor, fullscreen = false, forceRewrite = false) {
|
|
323
323
|
return forceRewrite || this.#screen === void 0 || !screensEqual(this.#screen, screen) || cursorPositionChanged(cursor, this.#cursor) || fullscreen !== this.#fullscreen;
|
|
324
324
|
}
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
/**
|
|
326
|
+
Erases the presented frame from the cursor upward.
|
|
327
|
+
|
|
328
|
+
`columns` is the terminal's current width. When it is narrower than the width
|
|
329
|
+
the frame was painted at, a reflowing emulator (xterm.js, Ghostty, kitty,
|
|
330
|
+
iTerm2, WezTerm, tmux, …) has already rewrapped every wider row onto several
|
|
331
|
+
physical rows before the resize event reaches us, so the erase has to cover
|
|
332
|
+
that physical footprint. Erasing only the logical row count leaves the frame's
|
|
333
|
+
top rows behind as ghosts, one more batch per resize event.
|
|
334
|
+
*/
|
|
335
|
+
clear(options = {}) {
|
|
336
|
+
this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(this.#physicalLineCount(options.columns)));
|
|
327
337
|
this.#lineCount = 0;
|
|
328
338
|
this.#cursor = void 0;
|
|
329
339
|
this.#cursorWasShown = false;
|
|
330
340
|
this.#fullscreen = false;
|
|
331
341
|
}
|
|
342
|
+
#physicalLineCount(columns) {
|
|
343
|
+
if (this.#screen === void 0 || this.#lineCount === 0 || columns === void 0 || columns < 1) return this.#lineCount;
|
|
344
|
+
let rows = 0;
|
|
345
|
+
for (const line of this.#screen.toRows()) rows += Math.max(1, Math.ceil(contentWidth(line) / columns));
|
|
346
|
+
return rows + (this.#fullscreen ? 0 : 1);
|
|
347
|
+
}
|
|
332
348
|
reset() {
|
|
333
349
|
this.#screen = void 0;
|
|
334
350
|
this.#lineCount = 0;
|
|
@@ -353,17 +369,36 @@ var ScreenPresenter = class {
|
|
|
353
369
|
this.#fullscreen = fullscreen;
|
|
354
370
|
}
|
|
355
371
|
};
|
|
372
|
+
/**
|
|
373
|
+
The columns a row occupies once written: everything up to its last visible
|
|
374
|
+
cell. Trailing unstyled blanks are trimmed by the serializer and never reach
|
|
375
|
+
the terminal, while styled blanks (a highlighted tab's padding) do.
|
|
376
|
+
*/
|
|
377
|
+
function contentWidth(line) {
|
|
378
|
+
let width = 0;
|
|
379
|
+
let end = 0;
|
|
380
|
+
for (const cell of line) {
|
|
381
|
+
width += cell.width;
|
|
382
|
+
if (!isBlank(cell)) end = width;
|
|
383
|
+
}
|
|
384
|
+
return end;
|
|
385
|
+
}
|
|
386
|
+
function isBlank(cell) {
|
|
387
|
+
const { style } = cell;
|
|
388
|
+
return (cell.grapheme === " " || cell.width === 0) && style.foreground === void 0 && style.background === void 0 && style.underlineColor === void 0 && style.underline === "none" && style.attributes === cellAttributes.none && cell.hyperlink === void 0;
|
|
389
|
+
}
|
|
356
390
|
function findFirstChangedRow(previous, next) {
|
|
357
391
|
const height = Math.max(previous.height, next.height);
|
|
358
392
|
for (let y = 0; y < height; y++) if (!rowsEqual(previous, next, y)) return y;
|
|
359
393
|
}
|
|
360
394
|
function rowsEqual(left, right, y) {
|
|
361
|
-
|
|
395
|
+
const length = Math.max(left.rowLength(y), right.rowLength(y));
|
|
396
|
+
for (let x = 0; x < length; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
|
|
362
397
|
return true;
|
|
363
398
|
}
|
|
364
399
|
function screensEqual(left, right) {
|
|
365
400
|
if (left.width !== right.width || left.height !== right.height) return false;
|
|
366
|
-
for (let y = 0; y < left.height; y++)
|
|
401
|
+
for (let y = 0; y < left.height; y++) if (!rowsEqual(left, right, y)) return false;
|
|
367
402
|
return true;
|
|
368
403
|
}
|
|
369
404
|
//#endregion
|
|
@@ -436,8 +471,9 @@ var TerminalSession = class {
|
|
|
436
471
|
willPresent(screen, options = {}) {
|
|
437
472
|
return this.#presenter.willPresent(screen, this.cursor.position, options.fullscreen, options.forceRewrite);
|
|
438
473
|
}
|
|
439
|
-
|
|
440
|
-
|
|
474
|
+
/** Erases the presented frame; pass the current `columns` after a resize so the erase covers rows the emulator rewrapped. */
|
|
475
|
+
clearFrame(options = {}) {
|
|
476
|
+
this.#presenter.clear(options);
|
|
441
477
|
}
|
|
442
478
|
resetFrame() {
|
|
443
479
|
this.#presenter.reset();
|
package/dist/terminal.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as CursorShape } from "./escapes-CB_6CWOE.js";
|
|
2
2
|
import { n as TerminalProgressState, t as ClipboardSelection } from "./osc-Cn0fw77g.js";
|
|
3
|
-
import { i as OutputStream, t as CapabilitiesStore } from "./store-
|
|
4
|
-
import { t as ColorProfile } from "./color-profile-
|
|
5
|
-
import { t as ColorPolicy } from "./color-policy-
|
|
6
|
-
import { n as Screen } from "./screen-
|
|
3
|
+
import { i as OutputStream, t as CapabilitiesStore } from "./store-C1P5fOUi.js";
|
|
4
|
+
import { t as ColorProfile } from "./color-profile-CyeHnG1T.js";
|
|
5
|
+
import { t as ColorPolicy } from "./color-policy-CCxuHIdD.js";
|
|
6
|
+
import { n as Screen } from "./screen-CgC2WlVM.js";
|
|
7
7
|
import { t as CursorPosition } from "./cursor-position-D2LAkRG0.js";
|
|
8
8
|
//#region src/input-parser.d.ts
|
|
9
9
|
type InputEvent = string | {
|
|
@@ -77,7 +77,10 @@ declare class TerminalSession {
|
|
|
77
77
|
readonly fullscreen?: boolean;
|
|
78
78
|
readonly forceRewrite?: boolean;
|
|
79
79
|
}): boolean;
|
|
80
|
-
|
|
80
|
+
/** Erases the presented frame; pass the current `columns` after a resize so the erase covers rows the emulator rewrapped. */
|
|
81
|
+
clearFrame(options?: {
|
|
82
|
+
readonly columns?: number;
|
|
83
|
+
}): void;
|
|
81
84
|
resetFrame(): void;
|
|
82
85
|
finishFrame(): void;
|
|
83
86
|
setColorPolicy(policy: ColorPolicy): void;
|
package/dist/terminal.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as TerminalInput, r as parseMouseEvent, t as TerminalSession } from "./session-
|
|
1
|
+
import { n as TerminalInput, r as parseMouseEvent, t as TerminalSession } from "./session-Cg6STjFV.js";
|
|
2
2
|
export { TerminalInput, TerminalSession, parseMouseEvent };
|
|
@@ -278,6 +278,7 @@ function transformAnsiLine(serializedSubtree, lineIndex, transformers) {
|
|
|
278
278
|
//#region src/wrap-text.ts
|
|
279
279
|
const wrapTextCache = new QuickLru({ maxSize: 4096 });
|
|
280
280
|
const wrapText = (text, maxWidth, wrapType) => {
|
|
281
|
+
if (wrapType === "none") return text;
|
|
281
282
|
const cacheKey = text + String(maxWidth) + String(wrapType);
|
|
282
283
|
const cachedText = wrapTextCache.get(cacheKey);
|
|
283
284
|
if (cachedText !== void 0) return cachedText;
|
|
@@ -386,6 +387,7 @@ const measureTextNode = function(node, width) {
|
|
|
386
387
|
if (dimensions.width <= width) return dimensions;
|
|
387
388
|
if (dimensions.width >= 1 && width > 0 && width < 1) return dimensions;
|
|
388
389
|
const textWrap = node.style?.textWrap ?? "wrap";
|
|
390
|
+
if (textWrap === "none") return dimensions;
|
|
389
391
|
const wrappedText = wrapText(text, width, textWrap);
|
|
390
392
|
return measureText(wrappedText);
|
|
391
393
|
};
|
|
@@ -562,7 +564,7 @@ const detectKittySupport = (stdin, stdout, onSupported) => {
|
|
|
562
564
|
//#endregion
|
|
563
565
|
//#region package.json
|
|
564
566
|
var name = "@alchemy.run/sigil";
|
|
565
|
-
var version = "0.0.0-alpha.
|
|
567
|
+
var version = "0.0.0-alpha.7";
|
|
566
568
|
//#endregion
|
|
567
569
|
//#region src/reconciler.ts
|
|
568
570
|
if (isSigilDev) await import("./devtools-DbthxoD1.js").catch(() => {});
|
package/package.json
CHANGED
|
@@ -85,8 +85,21 @@ export type Capabilities = {
|
|
|
85
85
|
/**
|
|
86
86
|
Current terminal dimensions in cells, plus pixel geometry once the
|
|
87
87
|
terminal has answered the query.
|
|
88
|
+
|
|
89
|
+
`source` tells where the cell dimensions come from. `"pty"` is the
|
|
90
|
+
stream's own `columns`/`rows`, updated by the OS on SIGWINCH — which says
|
|
91
|
+
nothing about whether the emulator has finished rewrapping its screen.
|
|
92
|
+
`"terminal"` is the emulator's in-band size report (mode 2048), which
|
|
93
|
+
arrives in the input stream after the rewrap and so describes the screen
|
|
94
|
+
exactly as later output will find it. While the terminal reports, its
|
|
95
|
+
size wins over the stream's.
|
|
88
96
|
*/
|
|
89
|
-
size: {
|
|
97
|
+
size: {
|
|
98
|
+
columns: number;
|
|
99
|
+
rows: number;
|
|
100
|
+
pixels: PixelGeometry | undefined;
|
|
101
|
+
source: "pty" | "terminal";
|
|
102
|
+
};
|
|
90
103
|
|
|
91
104
|
platform: NodeJS.Platform;
|
|
92
105
|
|
|
@@ -580,6 +593,7 @@ export function detectCapabilities({ stdout = process.stdout }: DetectOptions =
|
|
|
580
593
|
? { columns: stdout.columns, rows: stdout.rows }
|
|
581
594
|
: terminalSize()),
|
|
582
595
|
pixels: undefined,
|
|
596
|
+
source: "pty",
|
|
583
597
|
},
|
|
584
598
|
focused: undefined,
|
|
585
599
|
theme: {
|
|
@@ -128,11 +128,17 @@ const createStore = (stdin: StoreStdin, stdout: StoreStdout): CapabilitiesStore
|
|
|
128
128
|
let resizeSubscribers = 0;
|
|
129
129
|
|
|
130
130
|
let focused: boolean | undefined;
|
|
131
|
+
// The size from the terminal's latest in-band report (mode 2048). While
|
|
132
|
+
// set it is authoritative: the report is ordered in the input stream after
|
|
133
|
+
// the emulator rewrapped, whereas the stream's `columns`/`rows` only say
|
|
134
|
+
// what the OS told the PTY, before or after the emulator caught up.
|
|
135
|
+
let reportedSize: { columns: number; rows: number } | undefined;
|
|
131
136
|
let snapshot: Capabilities | undefined;
|
|
132
137
|
let snapshotQuery: TerminalQueryResult | undefined;
|
|
133
138
|
let snapshotColumns: number | undefined;
|
|
134
139
|
let snapshotRows: number | undefined;
|
|
135
140
|
let snapshotFocused: boolean | undefined;
|
|
141
|
+
let snapshotReportedSize: typeof reportedSize;
|
|
136
142
|
|
|
137
143
|
const current = (): Capabilities => {
|
|
138
144
|
const query = getTerminalQuery(stdout);
|
|
@@ -142,15 +148,20 @@ const createStore = (stdin: StoreStdin, stdout: StoreStdout): CapabilitiesStore
|
|
|
142
148
|
query !== snapshotQuery ||
|
|
143
149
|
columns !== snapshotColumns ||
|
|
144
150
|
rows !== snapshotRows ||
|
|
145
|
-
focused !== snapshotFocused
|
|
151
|
+
focused !== snapshotFocused ||
|
|
152
|
+
reportedSize !== snapshotReportedSize
|
|
146
153
|
) {
|
|
147
154
|
const detected = detectCapabilities({ stdout });
|
|
148
155
|
const applied = query ? applyTerminalQuery(detected, query) : detected;
|
|
149
|
-
|
|
156
|
+
const sized = reportedSize
|
|
157
|
+
? { ...applied, size: { ...applied.size, ...reportedSize, source: "terminal" as const } }
|
|
158
|
+
: applied;
|
|
159
|
+
snapshot = focused === undefined ? sized : { ...sized, focused };
|
|
150
160
|
snapshotQuery = query;
|
|
151
161
|
snapshotColumns = columns;
|
|
152
162
|
snapshotRows = rows;
|
|
153
163
|
snapshotFocused = focused;
|
|
164
|
+
snapshotReportedSize = reportedSize;
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
return snapshot;
|
|
@@ -217,6 +228,9 @@ const createStore = (stdin: StoreStdin, stdout: StoreStdout): CapabilitiesStore
|
|
|
217
228
|
integration?.setReportFeed?.(false);
|
|
218
229
|
removeExitHandler?.();
|
|
219
230
|
removeExitHandler = undefined;
|
|
231
|
+
// No further reports will arrive; the stream is the only size source
|
|
232
|
+
// again and the last report would go stale on the next resize.
|
|
233
|
+
reportedSize = undefined;
|
|
220
234
|
}
|
|
221
235
|
};
|
|
222
236
|
|
|
@@ -255,6 +269,7 @@ const createStore = (stdin: StoreStdin, stdout: StoreStdout): CapabilitiesStore
|
|
|
255
269
|
if (resize) {
|
|
256
270
|
const [, rows, columns, pixelHeight, pixelWidth] = resize.map(Number);
|
|
257
271
|
if (columns && rows) {
|
|
272
|
+
reportedSize = { columns, rows };
|
|
258
273
|
patchTerminalQuery(stdout, {
|
|
259
274
|
textAreaPixels:
|
|
260
275
|
pixelWidth && pixelHeight ? { width: pixelWidth, height: pixelHeight } : undefined,
|
package/src/components/Text.tsx
CHANGED
|
@@ -59,7 +59,7 @@ export type Props = {
|
|
|
59
59
|
readonly inverse?: boolean;
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off.
|
|
62
|
+
This property tells Ink to wrap or truncate text if its width is larger than the container. If `wrap` is passed (the default), Ink will wrap text and split it into multiple lines. If `hard` is passed, Ink will fill each line to the full column width, breaking words as necessary. If `truncate-*` is passed, Ink will truncate text instead, resulting in one line of text with the rest cut off. If `none` is passed, the text is neither wrapped nor truncated — it overflows the container (and the screen's nominal width), which is intended for log lines committed to scrollback (e.g. inside `<Static>`), not for live regions.
|
|
63
63
|
*/
|
|
64
64
|
readonly wrap?: Styles["textWrap"];
|
|
65
65
|
|
package/src/dom.ts
CHANGED
|
@@ -243,6 +243,12 @@ const measureTextNode = function (node: DOMNode, width: number): { width: number
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
const textWrap = node.style?.textWrap ?? "wrap";
|
|
246
|
+
|
|
247
|
+
// `none` keeps the intrinsic dimensions and overflows the container.
|
|
248
|
+
if (textWrap === "none") {
|
|
249
|
+
return dimensions;
|
|
250
|
+
}
|
|
251
|
+
|
|
246
252
|
const wrappedText = wrapText(text, width, textWrap);
|
|
247
253
|
|
|
248
254
|
return measureText(wrappedText);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMemo } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { getWindowSize } from "#/utils.ts";
|
|
3
|
+
import { useCapabilities } from "#/hooks/use-capabilities.ts";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
Dimensions of the terminal window.
|
|
@@ -20,22 +19,12 @@ export type WindowSize = {
|
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
21
|
A React hook that returns the current terminal window dimensions and re-renders the component whenever the terminal is resized.
|
|
22
|
+
|
|
23
|
+
Reads the capabilities store, so on terminals that send in-band size reports
|
|
24
|
+
(mode 2048) the dimensions are the emulator's own, arriving after it has
|
|
25
|
+
rewrapped its screen; elsewhere they are the stream's `columns`/`rows`.
|
|
23
26
|
*/
|
|
24
27
|
export const useWindowSize = (): WindowSize => {
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
const onResize = () => {
|
|
30
|
-
setSize(getWindowSize(stdout));
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
stdout.on("resize", onResize);
|
|
34
|
-
|
|
35
|
-
return () => {
|
|
36
|
-
stdout.off("resize", onResize);
|
|
37
|
-
};
|
|
38
|
-
}, [stdout]);
|
|
39
|
-
|
|
40
|
-
return size;
|
|
28
|
+
const { size } = useCapabilities();
|
|
29
|
+
return useMemo(() => ({ columns: size.columns, rows: size.rows }), [size.columns, size.rows]);
|
|
41
30
|
};
|