@oh-my-pi/pi-tui 17.1.7 → 17.1.8
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 +7 -0
- package/package.json +3 -3
- package/src/components/markdown.ts +32 -16
- package/src/terminal.ts +7 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.8] - 2026-07-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed wrapped Markdown list continuations losing their hanging indentation in narrow terminal layouts.
|
|
10
|
+
- Fixed an issue where emergency exits from fullscreen overlays could leave the Kitty keyboard protocol active, corrupting Arrow Up input in the terminal after exiting.
|
|
11
|
+
|
|
5
12
|
## [17.1.7] - 2026-07-27
|
|
6
13
|
|
|
7
14
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.8",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.1.8",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.1.8",
|
|
42
42
|
"lru-cache": "11.5.2",
|
|
43
43
|
"marked": "^18.0.6"
|
|
44
44
|
},
|
|
@@ -1874,9 +1874,10 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
1874
1874
|
);
|
|
1875
1875
|
const tokenLineOffsets = [0];
|
|
1876
1876
|
for (const line of renderedTokenLines) {
|
|
1877
|
-
//
|
|
1878
|
-
//
|
|
1879
|
-
|
|
1877
|
+
// Lists wrap while their structural prefixes are still available, so
|
|
1878
|
+
// continuation rows retain the correct hanging indent. Re-wrapping the
|
|
1879
|
+
// flattened rows here would discard that structure.
|
|
1880
|
+
if (token.type === "list" || TERMINAL.isImageLine(line) || isOsc66Line(line)) {
|
|
1880
1881
|
wrappedLines.push(line);
|
|
1881
1882
|
} else {
|
|
1882
1883
|
wrappedLines.push(...wrapTextWithAnsi(line, contentWidth));
|
|
@@ -2273,7 +2274,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
2273
2274
|
}
|
|
2274
2275
|
|
|
2275
2276
|
case "list": {
|
|
2276
|
-
const listLines = this.#renderList(token as ListToken, 0, styleContext);
|
|
2277
|
+
const listLines = this.#renderList(token as ListToken, 0, width, styleContext);
|
|
2277
2278
|
lines.push(...listLines);
|
|
2278
2279
|
// Don't add spacing after lists if a space token follows
|
|
2279
2280
|
// (the space token will handle it)
|
|
@@ -2589,46 +2590,60 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
2589
2590
|
/**
|
|
2590
2591
|
* Render a list with proper nesting support
|
|
2591
2592
|
*/
|
|
2592
|
-
#renderList(token: ListToken, depth: number, styleContext?: InlineStyleContext): string[] {
|
|
2593
|
+
#renderList(token: ListToken, depth: number, width: number, styleContext?: InlineStyleContext): string[] {
|
|
2593
2594
|
const lines: string[] = [];
|
|
2594
2595
|
const indent = " ".repeat(depth);
|
|
2595
2596
|
// Use the list's start property (defaults to 1 for ordered lists)
|
|
2596
2597
|
const startNumber = token.start ?? 1;
|
|
2598
|
+
const pushWrapped = (text: string, firstPrefix: string, continuationPrefix: string): void => {
|
|
2599
|
+
const prefixWidth = visibleWidth(firstPrefix);
|
|
2600
|
+
if (prefixWidth >= width) {
|
|
2601
|
+
lines.push(truncateToWidth(firstPrefix, width, Ellipsis.Omit));
|
|
2602
|
+
lines.push(...wrapTextWithAnsi(text, Math.max(1, width)));
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const bodyWidth = width - prefixWidth;
|
|
2606
|
+
const wrapped = wrapTextWithAnsi(text, bodyWidth);
|
|
2607
|
+
if (wrapped.length === 0) {
|
|
2608
|
+
lines.push(firstPrefix);
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
lines.push(firstPrefix + wrapped[0]);
|
|
2612
|
+
for (let lineIndex = 1; lineIndex < wrapped.length; lineIndex++) {
|
|
2613
|
+
lines.push(continuationPrefix + wrapped[lineIndex]);
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2597
2616
|
|
|
2598
2617
|
for (let i = 0; i < token.items.length; i++) {
|
|
2599
2618
|
const item = token.items[i];
|
|
2600
2619
|
const bullet = token.ordered ? `${startNumber + i}. ` : "- ";
|
|
2620
|
+
const firstPrefix = indent + this.#theme.listBullet(bullet);
|
|
2601
2621
|
// Continuation rows align under the item text, so the hang matches the
|
|
2602
2622
|
// actual bullet width (`10. ` is 4 cells, not 2).
|
|
2603
|
-
const continuationIndent = indent + padding(bullet
|
|
2623
|
+
const continuationIndent = indent + padding(visibleWidth(bullet));
|
|
2604
2624
|
|
|
2605
2625
|
// Process item tokens; nested-list lines arrive structurally tagged and
|
|
2606
2626
|
// already carry their own full indent.
|
|
2607
|
-
const itemLines = this.#renderListItem(item.tokens || [], depth, styleContext);
|
|
2627
|
+
const itemLines = this.#renderListItem(item.tokens || [], depth, width, styleContext);
|
|
2608
2628
|
|
|
2609
2629
|
if (itemLines.length > 0) {
|
|
2610
2630
|
const firstLine = itemLines[0]!;
|
|
2611
2631
|
if (firstLine.nested) {
|
|
2612
|
-
// Nested list first - keep as-is (already has full indent)
|
|
2613
2632
|
lines.push(firstLine.text);
|
|
2614
2633
|
} else {
|
|
2615
|
-
|
|
2616
|
-
lines.push(indent + this.#theme.listBullet(bullet) + firstLine.text);
|
|
2634
|
+
pushWrapped(firstLine.text, firstPrefix, continuationIndent);
|
|
2617
2635
|
}
|
|
2618
2636
|
|
|
2619
|
-
// Rest of the lines
|
|
2620
2637
|
for (let j = 1; j < itemLines.length; j++) {
|
|
2621
2638
|
const line = itemLines[j]!;
|
|
2622
2639
|
if (line.nested) {
|
|
2623
|
-
// Nested list line - already has full indent
|
|
2624
2640
|
lines.push(line.text);
|
|
2625
2641
|
} else {
|
|
2626
|
-
|
|
2627
|
-
lines.push(continuationIndent + line.text);
|
|
2642
|
+
pushWrapped(line.text, continuationIndent, continuationIndent);
|
|
2628
2643
|
}
|
|
2629
2644
|
}
|
|
2630
2645
|
} else {
|
|
2631
|
-
lines.push(
|
|
2646
|
+
lines.push(firstPrefix);
|
|
2632
2647
|
}
|
|
2633
2648
|
}
|
|
2634
2649
|
|
|
@@ -2644,6 +2659,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
2644
2659
|
#renderListItem(
|
|
2645
2660
|
tokens: Token[],
|
|
2646
2661
|
parentDepth: number,
|
|
2662
|
+
width: number,
|
|
2647
2663
|
styleContext?: InlineStyleContext,
|
|
2648
2664
|
): Array<{ text: string; nested: boolean }> {
|
|
2649
2665
|
const lines: Array<{ text: string; nested: boolean }> = [];
|
|
@@ -2652,7 +2668,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
2652
2668
|
if (token.type === "list") {
|
|
2653
2669
|
// Nested list - render with one additional indent level
|
|
2654
2670
|
// These lines carry their own indent, so tag them for pass-through
|
|
2655
|
-
const nestedLines = this.#renderList(token as ListToken, parentDepth + 1, styleContext);
|
|
2671
|
+
const nestedLines = this.#renderList(token as ListToken, parentDepth + 1, width, styleContext);
|
|
2656
2672
|
for (const nestedLine of nestedLines) {
|
|
2657
2673
|
lines.push({ text: nestedLine, nested: true });
|
|
2658
2674
|
}
|
package/src/terminal.ts
CHANGED
|
@@ -275,17 +275,15 @@ export function emergencyTerminalRestore(): void {
|
|
|
275
275
|
restoreTerminalStderr();
|
|
276
276
|
const terminal = activeTerminal;
|
|
277
277
|
if (terminal) {
|
|
278
|
-
|
|
279
|
-
//
|
|
280
|
-
// state and exits it on the normal shutdown path. Only crash paths
|
|
281
|
-
// with a fullscreen overlay still hold the alt buffer here. The
|
|
282
|
-
// leave sequence is gated on the tracked state because it is NOT a
|
|
283
|
-
// universally safe no-op: Windows' VT dispatcher homes the cursor
|
|
284
|
-
// on DECRST 1049 even when the alt buffer is inactive.
|
|
278
|
+
// Keyboard enhancement state is screen-local: pop the alt-screen
|
|
279
|
+
// frame before leaving it, then let stop() pop omp's main-screen frame.
|
|
285
280
|
if (altScreenActive) {
|
|
286
|
-
|
|
281
|
+
const keyboardExit =
|
|
282
|
+
terminal.keyboardEnhancementExitSequence ?? (terminal.kittyEnableSequence ? "\x1b[<u" : "");
|
|
283
|
+
terminal.write(`${keyboardExit}\x1b[?1049l`);
|
|
287
284
|
altScreenActive = false;
|
|
288
285
|
}
|
|
286
|
+
terminal.stop();
|
|
289
287
|
terminal.showCursor(true);
|
|
290
288
|
} else if (terminalEverStarted && !isTerminalHeadless()) {
|
|
291
289
|
// Blind restore only if we know a terminal was started but lost track of it
|
|
@@ -305,7 +303,7 @@ export function emergencyTerminalRestore(): void {
|
|
|
305
303
|
// actually holds it — on Windows, DECRST 1049 on the main
|
|
306
304
|
// buffer homes the cursor (unconditional CursorRestoreState
|
|
307
305
|
// with no prior save), corrupting the shell handoff on exit.
|
|
308
|
-
(altScreenActive ? "\x1b[?1049l" : "") +
|
|
306
|
+
(altScreenActive ? "\x1b[?1049l\x1b[?1l\x1b>\x1b[<u" : "") + // Leave alt; reset main keyboard
|
|
309
307
|
"\x1b[?25h", // Show cursor
|
|
310
308
|
);
|
|
311
309
|
altScreenActive = false;
|