@linxiraos/pi-tui 1.1.8 → 1.1.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/CHANGELOG.md +2 -3
- package/package.json +64 -70
- package/src/components/composer/band.ts +6 -2
- package/src/components/markdown.ts +60 -2
- package/src/tui.ts +58 -24
- package/THIRD-PARTY-NOTICES.txt +0 -22909
- package/dist/types/autocomplete.d.ts +0 -127
- package/dist/types/bracketed-paste.d.ts +0 -51
- package/dist/types/components/box.d.ts +0 -33
- package/dist/types/components/cancellable-loader.d.ts +0 -23
- package/dist/types/components/composer/band.d.ts +0 -2
- package/dist/types/components/composer/borderless.d.ts +0 -8
- package/dist/types/components/composer/box.d.ts +0 -2
- package/dist/types/components/composer/claude.d.ts +0 -8
- package/dist/types/components/composer/field.d.ts +0 -3
- package/dist/types/components/composer/index.d.ts +0 -10
- package/dist/types/components/composer/pi.d.ts +0 -2
- package/dist/types/components/composer/rail.d.ts +0 -3
- package/dist/types/components/composer/registry.d.ts +0 -19
- package/dist/types/components/composer/rule.d.ts +0 -7
- package/dist/types/components/composer/types.d.ts +0 -96
- package/dist/types/components/editor.d.ts +0 -225
- package/dist/types/components/image.d.ts +0 -163
- package/dist/types/components/input.d.ts +0 -27
- package/dist/types/components/loader.d.ts +0 -33
- package/dist/types/components/markdown.d.ts +0 -100
- package/dist/types/components/scroll-view.d.ts +0 -64
- package/dist/types/components/select-list.d.ts +0 -80
- package/dist/types/components/settings-list.d.ts +0 -133
- package/dist/types/components/spacer.d.ts +0 -13
- package/dist/types/components/tab-bar.d.ts +0 -91
- package/dist/types/components/text.d.ts +0 -29
- package/dist/types/components/truncated-text.d.ts +0 -12
- package/dist/types/debug-server.d.ts +0 -27
- package/dist/types/deccara.d.ts +0 -49
- package/dist/types/desktop-notify.d.ts +0 -52
- package/dist/types/editor-component.d.ts +0 -38
- package/dist/types/fuzzy.d.ts +0 -48
- package/dist/types/index.d.ts +0 -34
- package/dist/types/keybindings.d.ts +0 -202
- package/dist/types/keys.d.ts +0 -210
- package/dist/types/kill-ring.d.ts +0 -20
- package/dist/types/kitty-graphics.d.ts +0 -76
- package/dist/types/latex-block.d.ts +0 -8
- package/dist/types/latex-to-unicode.d.ts +0 -41
- package/dist/types/loop-watchdog.d.ts +0 -49
- package/dist/types/mouse.d.ts +0 -67
- package/dist/types/stdin-buffer.d.ts +0 -65
- package/dist/types/symbols.d.ts +0 -25
- package/dist/types/terminal-capabilities.d.ts +0 -369
- package/dist/types/terminal-multiplexer.d.ts +0 -4
- package/dist/types/terminal.d.ts +0 -251
- package/dist/types/tmux.d.ts +0 -6
- package/dist/types/ttyid.d.ts +0 -9
- package/dist/types/tui.d.ts +0 -389
- package/dist/types/utils.d.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.9] - 2026-09-05
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- OMP sync v18.1.2–v18.1.5: sub-frame history ownership (the frame provider owns history), CoW worktree cloning support, and renderer fixes.
|
|
7
|
+
- Sidebar gutter: provider-render frames now honor the reserved main width and paint the right gutter column (sidebar was dead code in production since the frame-provider refactor); overlay close clears the painted column.
|
|
9
8
|
|
|
10
9
|
## [1.1.3] - 2026-08-25
|
|
11
10
|
|
package/package.json
CHANGED
|
@@ -1,72 +1,66 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"./components/*": {
|
|
67
|
-
"types": "./dist/types/components/*.d.ts",
|
|
68
|
-
"import": "./src/components/*.ts"
|
|
69
|
-
},
|
|
70
|
-
"./*.js": "./src/*.ts"
|
|
71
|
-
}
|
|
2
|
+
"exports": {
|
|
3
|
+
"./*.js": "./src/*.ts",
|
|
4
|
+
"./components/*": {
|
|
5
|
+
"import": "./src/components/*.ts",
|
|
6
|
+
"types": "./src/components/*.ts"
|
|
7
|
+
},
|
|
8
|
+
"./*": {
|
|
9
|
+
"import": "./src/*.ts",
|
|
10
|
+
"types": "./src/*.ts"
|
|
11
|
+
},
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./src/index.ts",
|
|
14
|
+
"types": "./src/index.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"bun": ">=1.3.14"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"kitty-vt-wasm": "^0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@linxiraos/pi-utils": "1.1.9",
|
|
30
|
+
"@linxiraos/pi-natives": "1.1.9"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "bun test --parallel test/*.test.ts",
|
|
34
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
35
|
+
"check": "tsgo -p tsconfig.json --noEmit"
|
|
36
|
+
},
|
|
37
|
+
"types": "./src/index.ts",
|
|
38
|
+
"main": "./src/index.ts",
|
|
39
|
+
"keywords": [
|
|
40
|
+
"tui",
|
|
41
|
+
"terminal",
|
|
42
|
+
"ui",
|
|
43
|
+
"text-editor",
|
|
44
|
+
"differential-rendering",
|
|
45
|
+
"typescript",
|
|
46
|
+
"cli"
|
|
47
|
+
],
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/Linxira-OS/linxira-zeta/issues"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"directory": "packages/tui",
|
|
53
|
+
"url": "git+https://github.com/Linxira-OS/linxira-zeta.git",
|
|
54
|
+
"type": "git"
|
|
55
|
+
},
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"contributors": [
|
|
58
|
+
"Mario Zechner"
|
|
59
|
+
],
|
|
60
|
+
"author": "Stencil Labs, Inc.",
|
|
61
|
+
"homepage": "https://linxira-os.github.io/zeta/",
|
|
62
|
+
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
63
|
+
"version": "1.1.9",
|
|
64
|
+
"name": "@linxiraos/pi-tui",
|
|
65
|
+
"type": "module"
|
|
72
66
|
}
|
|
@@ -24,9 +24,13 @@ export const bandComposerStyle: ComposerStyle = {
|
|
|
24
24
|
return 0;
|
|
25
25
|
},
|
|
26
26
|
|
|
27
|
-
renderTop(ctx: ComposerChromeContext): string
|
|
27
|
+
renderTop(ctx: ComposerChromeContext): string {
|
|
28
28
|
const { topBorder, width } = ctx;
|
|
29
|
-
|
|
29
|
+
// The band always owns one chrome row (`verticalChrome: 1`): keep it
|
|
30
|
+
// reserved while the status line has nothing to show yet — the startup
|
|
31
|
+
// prepaint mounts the editor before the session-aware status line
|
|
32
|
+
// attaches — so the band fills in later without shifting the layout.
|
|
33
|
+
if (!topBorder?.content) return "";
|
|
30
34
|
// The band builder already sizes its groups + gauge to the full width;
|
|
31
35
|
// truncation only guards against a stale provider during resize.
|
|
32
36
|
return topBorder.width > width ? truncateToWidth(topBorder.content, width) : topBorder.content;
|
|
@@ -1254,6 +1254,52 @@ function lexDocument(text: string): Token[] {
|
|
|
1254
1254
|
return lexWindowed(text);
|
|
1255
1255
|
}
|
|
1256
1256
|
|
|
1257
|
+
/** A hyperlink as the renderer sees it: inline `[text](href)`, `<autolink>`, bare GFM URL, or reference link. */
|
|
1258
|
+
export interface MarkdownLink {
|
|
1259
|
+
/** Visible link text (equals `href` for autolinks and bare URLs). */
|
|
1260
|
+
text: string;
|
|
1261
|
+
/** Destination exactly as marked resolved it (references resolved, no normalization). */
|
|
1262
|
+
href: string;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Every link token in `text`, in document order, from the same configured
|
|
1267
|
+
* lexer the renderer uses — so fenced code, code spans, escapes, reference
|
|
1268
|
+
* definitions and the GFM autolink rules agree with what is drawn on screen.
|
|
1269
|
+
* Duplicate hrefs are kept; callers decide how to fold them.
|
|
1270
|
+
*/
|
|
1271
|
+
export function extractMarkdownLinks(text: string): MarkdownLink[] {
|
|
1272
|
+
const links: MarkdownLink[] = [];
|
|
1273
|
+
const walk = (tokens: readonly Token[] | undefined): void => {
|
|
1274
|
+
if (!tokens) return;
|
|
1275
|
+
for (const token of tokens) {
|
|
1276
|
+
if (token.type === "link") {
|
|
1277
|
+
const link = token as Tokens.Link;
|
|
1278
|
+
if (typeof link.href === "string" && link.href.length > 0) {
|
|
1279
|
+
links.push({
|
|
1280
|
+
text: typeof link.text === "string" && link.text.length > 0 ? link.text : link.href,
|
|
1281
|
+
href: link.href,
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
// Containers: paragraphs, emphasis, lists, blockquotes, table cells.
|
|
1287
|
+
const any = token as {
|
|
1288
|
+
tokens?: Token[];
|
|
1289
|
+
items?: Token[];
|
|
1290
|
+
header?: Array<{ tokens?: Token[] }>;
|
|
1291
|
+
rows?: Array<Array<{ tokens?: Token[] }>>;
|
|
1292
|
+
};
|
|
1293
|
+
walk(any.tokens);
|
|
1294
|
+
walk(any.items);
|
|
1295
|
+
if (any.header) for (const cell of any.header) walk(cell.tokens);
|
|
1296
|
+
if (any.rows) for (const row of any.rows) for (const cell of row) walk(cell.tokens);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
walk(lexDocument(text));
|
|
1300
|
+
return links;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1257
1303
|
/** Drop all L2 cache entries. Call on theme change to prevent stale styled output. */
|
|
1258
1304
|
export function clearRenderCache(): void {
|
|
1259
1305
|
renderCache.clear();
|
|
@@ -1300,6 +1346,15 @@ export interface HighlightStreamSession {
|
|
|
1300
1346
|
push(chunk: string): string;
|
|
1301
1347
|
}
|
|
1302
1348
|
|
|
1349
|
+
/** Collect distinct hyperlink destinations using the renderer's Markdown grammar, excluding images and code. */
|
|
1350
|
+
export function getMarkdownLinkUrls(text: string): string[] {
|
|
1351
|
+
const urls = new Set<string>();
|
|
1352
|
+
markdownParser.walkTokens(markdownParser.lexer(text), token => {
|
|
1353
|
+
if (token.type === "link" && typeof token.href === "string") urls.add(token.href);
|
|
1354
|
+
});
|
|
1355
|
+
return [...urls];
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1303
1358
|
/**
|
|
1304
1359
|
* Theme functions for markdown elements.
|
|
1305
1360
|
* Each function takes text and returns styled text with ANSI codes.
|
|
@@ -1308,6 +1363,8 @@ export interface MarkdownTheme {
|
|
|
1308
1363
|
heading: (text: string) => string;
|
|
1309
1364
|
link: (text: string) => string;
|
|
1310
1365
|
linkUrl: (text: string) => string;
|
|
1366
|
+
/** Resolve the OSC 8 destination without changing visible text; undefined preserves the authored URL. */
|
|
1367
|
+
resolveLink?: (href: string) => string | undefined;
|
|
1311
1368
|
code: (text: string) => string;
|
|
1312
1369
|
codeBlock: (text: string) => string;
|
|
1313
1370
|
codeBlockBorder: (text: string) => string;
|
|
@@ -3162,7 +3219,8 @@ export class Markdown implements Component {
|
|
|
3162
3219
|
const linkText = this.#renderInlineTokens(token.tokens || [], resolvedStyleContext);
|
|
3163
3220
|
const styledLinkText = this.#theme.link(this.#theme.underline(linkText));
|
|
3164
3221
|
const href = typeof token.href === "string" ? token.href : "";
|
|
3165
|
-
const
|
|
3222
|
+
const target = (href && this.#theme.resolveLink?.(href)) || href;
|
|
3223
|
+
const clickableLinkText = formatHyperlink(styledLinkText, target);
|
|
3166
3224
|
// If link text matches href, only show the link once. A missing
|
|
3167
3225
|
// href (malformed/partial link token) renders as plain link text
|
|
3168
3226
|
// instead of crashing the renderer or emitting an empty "()"
|
|
@@ -3175,7 +3233,7 @@ export class Markdown implements Component {
|
|
|
3175
3233
|
result += clickableLinkText + stylePrefix;
|
|
3176
3234
|
else {
|
|
3177
3235
|
const styledLinkUrl = this.#theme.linkUrl(`(${href})`);
|
|
3178
|
-
result += `${clickableLinkText} ${formatHyperlink(styledLinkUrl,
|
|
3236
|
+
result += `${clickableLinkText} ${formatHyperlink(styledLinkUrl, target)}${stylePrefix}`;
|
|
3179
3237
|
}
|
|
3180
3238
|
break;
|
|
3181
3239
|
}
|
package/src/tui.ts
CHANGED
|
@@ -711,6 +711,13 @@ export class TUI extends Container {
|
|
|
711
711
|
#previousFrameLength = 0;
|
|
712
712
|
#previousWidth = 0;
|
|
713
713
|
#previousHeight = 0;
|
|
714
|
+
/**
|
|
715
|
+
* Physical terminal width of the last provider frame. `#previousWidth`
|
|
716
|
+
* tracks the composed (main-area) width once a sidebar reservation is
|
|
717
|
+
* active, while scrollback reflow follows the terminal's own wrap width —
|
|
718
|
+
* resize-replay dedup must compare physical to physical.
|
|
719
|
+
*/
|
|
720
|
+
#previousPhysicalWidth = 0;
|
|
714
721
|
#focusedComponent: Component | null = null;
|
|
715
722
|
#debugServer: TuiDebugServer | undefined;
|
|
716
723
|
#debugPaint:
|
|
@@ -905,6 +912,17 @@ export class TUI extends Container {
|
|
|
905
912
|
const next = width !== null && Number.isInteger(width) && width > 0 ? width : null;
|
|
906
913
|
if (this.#mainWidthOverride === next) return;
|
|
907
914
|
this.#mainWidthOverride = next;
|
|
915
|
+
// The composed row width changes, so already-committed scrollback would
|
|
916
|
+
// sit shredded at the old width. Re-offer finalized history and force a
|
|
917
|
+
// full viewport repaint on the next frame, mirroring the settled-resize
|
|
918
|
+
// replay in #prepareResizeReplay.
|
|
919
|
+
if (this.#hasEverRendered) {
|
|
920
|
+
const provider = this.#frameProvider;
|
|
921
|
+
if (provider?.beginHistoryReplay) {
|
|
922
|
+
provider.beginHistoryReplay();
|
|
923
|
+
this.#forceViewportRepaintOnNextRender = true;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
908
926
|
this.requestRender();
|
|
909
927
|
}
|
|
910
928
|
|
|
@@ -2357,14 +2375,14 @@ export class TUI extends Container {
|
|
|
2357
2375
|
const coalesced = coalesceAdjacentSgr(line);
|
|
2358
2376
|
return coalesced + (line.includes("\x1b]8;") ? LINE_TERMINATOR : SEGMENT_RESET);
|
|
2359
2377
|
}
|
|
2360
|
-
#renderProviderFrame(
|
|
2378
|
+
#renderProviderFrame(mainWidth: number, rawWidth: number, height: number): void {
|
|
2361
2379
|
const provider = this.#frameProvider;
|
|
2362
|
-
if (!provider ||
|
|
2380
|
+
if (!provider || mainWidth <= 0 || height <= 0) return;
|
|
2363
2381
|
this.#debugNextWindowTop = 0;
|
|
2364
2382
|
let plan: TerminalFramePlan;
|
|
2365
2383
|
do {
|
|
2366
2384
|
this.#imageBudget.beginPass();
|
|
2367
|
-
plan = provider.renderFrame({ columns:
|
|
2385
|
+
plan = provider.renderFrame({ columns: mainWidth, rows: height });
|
|
2368
2386
|
} while (this.#imageBudget.endPass());
|
|
2369
2387
|
let viewport = Array.from(plan.viewport);
|
|
2370
2388
|
if (viewport.length > height) {
|
|
@@ -2374,7 +2392,36 @@ export class TUI extends Container {
|
|
|
2374
2392
|
viewport = viewport.slice(0, height);
|
|
2375
2393
|
}
|
|
2376
2394
|
if (this.#maybeDeferGhosttyInitialImagePaint()) return;
|
|
2377
|
-
this.#emitPlanFrame(
|
|
2395
|
+
this.#emitPlanFrame(mainWidth, height, viewport, plan.history, provider);
|
|
2396
|
+
this.#previousPhysicalWidth = rawWidth;
|
|
2397
|
+
this.#paintGutter(mainWidth, rawWidth, height, viewport.length);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
/**
|
|
2401
|
+
* Paint (or clear) the right-hand gutter column after a frame's content
|
|
2402
|
+
* block, inside the same synchronous paint. Content is composed at
|
|
2403
|
+
* `mainWidth`, so absolute-positioned gutter writes at column `mainWidth + 1`
|
|
2404
|
+
* compose with it without touching the frame or scrollback. The gutter
|
|
2405
|
+
* component composes into the column width it owns (`rawWidth - mainWidth`).
|
|
2406
|
+
* When no gutter applies this frame — component cleared, or an overlay frame
|
|
2407
|
+
* composing at full width — a previously painted gutter is erased row by row.
|
|
2408
|
+
*/
|
|
2409
|
+
#paintGutter(mainWidth: number, rawWidth: number, height: number, viewportRows: number): void {
|
|
2410
|
+
const gutter = this.#gutterComponent && mainWidth < rawWidth ? this.#gutterComponent : null;
|
|
2411
|
+
if (gutter) {
|
|
2412
|
+
const rows = gutter.render(rawWidth - mainWidth);
|
|
2413
|
+
const seq = this.#gutterPaintSequence(rows, rawWidth - mainWidth, mainWidth, viewportRows, 0);
|
|
2414
|
+
if (seq) this.terminal.write(seq);
|
|
2415
|
+
this.#paintedGutterRows = rows;
|
|
2416
|
+
} else if (this.#paintedGutterRows !== null) {
|
|
2417
|
+
// Gutter removed: clear the margin column once via absolute EL per row.
|
|
2418
|
+
const col = mainWidth + 1;
|
|
2419
|
+
let seq = "";
|
|
2420
|
+
for (let row = 0; row < height; row++) seq += `\x1b[${row + 1};${col}H\x1b[K`;
|
|
2421
|
+
seq += "\x1b[H";
|
|
2422
|
+
this.terminal.write(seq);
|
|
2423
|
+
this.#paintedGutterRows = null;
|
|
2424
|
+
}
|
|
2378
2425
|
}
|
|
2379
2426
|
/**
|
|
2380
2427
|
* Re-offer finalized history once after a settled resize.
|
|
@@ -2395,7 +2442,7 @@ export class TUI extends Container {
|
|
|
2395
2442
|
const size = `${width}x${height}`;
|
|
2396
2443
|
if (
|
|
2397
2444
|
!this.#hasEverRendered ||
|
|
2398
|
-
(this.#
|
|
2445
|
+
(this.#previousPhysicalWidth === width && this.#previousHeight === height) ||
|
|
2399
2446
|
this.#resizeReplaySize === size ||
|
|
2400
2447
|
this.#resizeScrollbackMode === "preserve"
|
|
2401
2448
|
) {
|
|
@@ -2412,7 +2459,7 @@ export class TUI extends Container {
|
|
|
2412
2459
|
this.#prepareForcedRender(true);
|
|
2413
2460
|
return;
|
|
2414
2461
|
}
|
|
2415
|
-
if (width === this.#
|
|
2462
|
+
if (width === this.#previousPhysicalWidth) return;
|
|
2416
2463
|
provider.beginHistoryReplay();
|
|
2417
2464
|
this.#forceViewportRepaintOnNextRender = true;
|
|
2418
2465
|
}
|
|
@@ -2724,8 +2771,11 @@ export class TUI extends Container {
|
|
|
2724
2771
|
return;
|
|
2725
2772
|
}
|
|
2726
2773
|
if (this.#frameProvider !== undefined) {
|
|
2774
|
+
const mainWidth = this.#effectiveMainWidth(width);
|
|
2775
|
+
// Physical size keys: scrollback reflow follows the terminal's wrap
|
|
2776
|
+
// width, not the composed main-area width.
|
|
2727
2777
|
this.#prepareResizeReplay(width, height);
|
|
2728
|
-
this.#renderProviderFrame(width, height);
|
|
2778
|
+
this.#renderProviderFrame(mainWidth, width, height);
|
|
2729
2779
|
return;
|
|
2730
2780
|
}
|
|
2731
2781
|
this.#renderChildrenFrame(width, this.#effectiveMainWidth(width), height);
|
|
@@ -2737,7 +2787,6 @@ export class TUI extends Container {
|
|
|
2737
2787
|
* mutable viewport. Nothing is ever appended to terminal history.
|
|
2738
2788
|
*/
|
|
2739
2789
|
#renderChildrenFrame(rawWidth: number, width: number, height: number): void {
|
|
2740
|
-
const gutter = this.#gutterComponent && width < rawWidth ? this.#gutterComponent : null;
|
|
2741
2790
|
let composed: readonly string[];
|
|
2742
2791
|
do {
|
|
2743
2792
|
this.#imageBudget.beginPass();
|
|
@@ -2747,22 +2796,7 @@ export class TUI extends Container {
|
|
|
2747
2796
|
this.#debugNextWindowTop = Math.max(0, composed.length - height);
|
|
2748
2797
|
const viewport = composed.length > height ? composed.slice(composed.length - height) : Array.from(composed);
|
|
2749
2798
|
this.#emitPlanFrame(width, height, viewport, undefined, undefined);
|
|
2750
|
-
|
|
2751
|
-
const rows = gutter.render(width);
|
|
2752
|
-
// Paint the freed right margin after the frame block: absolute-positioned
|
|
2753
|
-
// writes at column width+1 never touch the composed frame or scrollback.
|
|
2754
|
-
const seq = this.#gutterPaintSequence(rows, rawWidth - width, width, viewport.length, 0);
|
|
2755
|
-
if (seq) this.terminal.write(seq);
|
|
2756
|
-
this.#paintedGutterRows = rows;
|
|
2757
|
-
} else if (this.#paintedGutterRows !== null) {
|
|
2758
|
-
// Gutter removed: clear the margin column once via absolute EL per row.
|
|
2759
|
-
const col = width + 1;
|
|
2760
|
-
let seq = "";
|
|
2761
|
-
for (let row = 0; row < height; row++) seq += `\x1b[${row + 1};${col}H\x1b[K`;
|
|
2762
|
-
seq += "\x1b[H";
|
|
2763
|
-
this.terminal.write(seq);
|
|
2764
|
-
this.#paintedGutterRows = null;
|
|
2765
|
-
}
|
|
2799
|
+
this.#paintGutter(width, rawWidth, height, viewport.length);
|
|
2766
2800
|
}
|
|
2767
2801
|
|
|
2768
2802
|
/** Stateless variant for overlay-composited windows and alt-screen frames. */
|