@oh-my-pi/pi-tui 17.0.7 → 17.0.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 +8 -0
- package/dist/types/terminal.d.ts +5 -5
- package/package.json +3 -3
- package/src/autocomplete.ts +7 -7
- package/src/components/markdown.ts +13 -2
- package/src/terminal.ts +24 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.8] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed an issue where OSC 8 hyperlinks with inline markup corrupted Markdown table column widths.
|
|
10
|
+
- Prevented duplicate autocomplete suggestions for slash commands and their matching aliases.
|
|
11
|
+
- Fixed terminal background color detection during screen refreshes (Ctrl+L) inside tmux when passthrough is enabled.
|
|
12
|
+
|
|
5
13
|
## [17.0.6] - 2026-07-20
|
|
6
14
|
|
|
7
15
|
### Fixed
|
package/dist/types/terminal.d.ts
CHANGED
|
@@ -105,11 +105,11 @@ export declare class ProcessTerminal implements Terminal {
|
|
|
105
105
|
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
106
106
|
/**
|
|
107
107
|
* Re-query the terminal background via a single OSC 11 probe. Reuses the
|
|
108
|
-
* startup
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* Bounded to one probe per call; no timers are
|
|
112
|
-
* or after the terminal is torn down.
|
|
108
|
+
* startup DA1-sentinel FIFO, pending/queued gating, parsing, dedup, and
|
|
109
|
+
* appearance callbacks. Inside tmux, only this explicit path wraps the query
|
|
110
|
+
* and sentinel together for passthrough to the outer terminal; startup and
|
|
111
|
+
* Mode 2031 probes remain direct. Bounded to one probe per call; no timers are
|
|
112
|
+
* armed. Suppressed while headless or after the terminal is torn down.
|
|
113
113
|
*/
|
|
114
114
|
refreshAppearance(): void;
|
|
115
115
|
onPrivateModeReport(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void;
|
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.0.
|
|
4
|
+
"version": "17.0.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.0.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.0.
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.0.8",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.0.8",
|
|
42
42
|
"lru-cache": "11.5.2",
|
|
43
43
|
"marked": "^18.0.6"
|
|
44
44
|
},
|
package/src/autocomplete.ts
CHANGED
|
@@ -307,7 +307,7 @@ function buildSlashCommandCompletions(commands: CommandEntry[], lowerPrefix: str
|
|
|
307
307
|
}
|
|
308
308
|
return fullDescMemo;
|
|
309
309
|
};
|
|
310
|
-
|
|
310
|
+
let best: (AutocompleteItem & { score: number }) | undefined;
|
|
311
311
|
|
|
312
312
|
const isSkillCommand = name.startsWith("skill:");
|
|
313
313
|
const nameScore =
|
|
@@ -318,30 +318,30 @@ function buildSlashCommandCompletions(commands: CommandEntry[], lowerPrefix: str
|
|
|
318
318
|
const primaryScore = Math.max(nameScore, descScore);
|
|
319
319
|
if (primaryScore > 0) {
|
|
320
320
|
const fullDesc = resolveFullDesc();
|
|
321
|
-
|
|
321
|
+
best = {
|
|
322
322
|
value: name,
|
|
323
323
|
label: "name" in cmd ? cmd.name : cmd.label,
|
|
324
324
|
score: primaryScore,
|
|
325
325
|
...(fullDesc && { description: fullDesc }),
|
|
326
|
-
}
|
|
326
|
+
};
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
if (lowerPrefix.length > 0) {
|
|
330
330
|
for (const alias of getCommandAliases(cmd)) {
|
|
331
331
|
if (alias === name) continue;
|
|
332
332
|
const aliasScore = scoreCommandTextMatch(lowerPrefix, alias.toLowerCase());
|
|
333
|
-
if (aliasScore === 0) continue;
|
|
333
|
+
if (aliasScore === 0 || (best && aliasScore <= best.score)) continue;
|
|
334
334
|
const fullDesc = resolveFullDesc();
|
|
335
|
-
|
|
335
|
+
best = {
|
|
336
336
|
value: alias,
|
|
337
337
|
label: alias,
|
|
338
338
|
score: aliasScore,
|
|
339
339
|
...(fullDesc && { description: fullDesc }),
|
|
340
|
-
}
|
|
340
|
+
};
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
return
|
|
344
|
+
return best ? [best] : [];
|
|
345
345
|
})
|
|
346
346
|
.sort((a, b) => b.score - a.score)
|
|
347
347
|
.map(({ score: _, ...rest }) => rest);
|
|
@@ -20,6 +20,16 @@ import {
|
|
|
20
20
|
|
|
21
21
|
const STRICT_STRIKETHROUGH_REGEX = /^(~~)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/;
|
|
22
22
|
|
|
23
|
+
// Marked treats the backslash in an ST-terminated OSC 8 sequence (`ESC \\`) as
|
|
24
|
+
// Markdown punctuation when it is immediately followed by markup such as a
|
|
25
|
+
// codespan backtick. Normalize well-formed OSC 8 prefixes to the equivalent BEL
|
|
26
|
+
// terminator before lexing so the control sequence stays opaque to Markdown.
|
|
27
|
+
const OSC8_ST_PREFIX_REGEX = /(\x1b\]8;[^\x07\x1b]*)\x1b\\/g;
|
|
28
|
+
|
|
29
|
+
function normalizeOsc8Terminators(text: string): string {
|
|
30
|
+
return text.replace(OSC8_ST_PREFIX_REGEX, "$1\x07");
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
// OSC 66 (Kitty text-sizing) heading spans are emitted as a single indivisible
|
|
24
34
|
// unit by the H1 render path. Like image-protocol lines, they must bypass
|
|
25
35
|
// ANSI wrapping and width padding: re-wrapping splits/normalizes the sized span
|
|
@@ -1097,7 +1107,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
1097
1107
|
defaultTextStyle?: DefaultTextStyle,
|
|
1098
1108
|
codeBlockIndent: number = 2,
|
|
1099
1109
|
) {
|
|
1100
|
-
this.#text = text;
|
|
1110
|
+
this.#text = normalizeOsc8Terminators(text);
|
|
1101
1111
|
this.#paddingX = paddingX;
|
|
1102
1112
|
this.#paddingY = paddingY;
|
|
1103
1113
|
this.#theme = theme;
|
|
@@ -1106,6 +1116,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
1106
1116
|
}
|
|
1107
1117
|
|
|
1108
1118
|
setText(text: string): boolean {
|
|
1119
|
+
text = normalizeOsc8Terminators(text);
|
|
1109
1120
|
// Equality guard: streaming re-emits identical text on ticks that carried
|
|
1110
1121
|
// no delta (throttled provider frames, reconciled tool-execution updates).
|
|
1111
1122
|
// Without this, the caller-side `#cachedLines` gets thrown away and the
|
|
@@ -2611,7 +2622,7 @@ export class Markdown implements Component, NativeScrollbackCommittedRows, Nativ
|
|
|
2611
2622
|
export function renderInlineMarkdown(text: string, mdTheme: MarkdownTheme, baseColor?: (t: string) => string): string {
|
|
2612
2623
|
// Guard against undefined/null during streaming — partial JSON can leave fields unpopulated.
|
|
2613
2624
|
if (typeof text !== "string") return (baseColor ?? (t => t))(text != null ? String(text) : "");
|
|
2614
|
-
const tokens = markdownParser.lexer(text);
|
|
2625
|
+
const tokens = markdownParser.lexer(normalizeOsc8Terminators(text));
|
|
2615
2626
|
const applyText = baseColor ?? ((t: string) => t);
|
|
2616
2627
|
let result = "";
|
|
2617
2628
|
for (const token of tokens) {
|
package/src/terminal.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
setOsc99Supported,
|
|
19
19
|
TERMINAL,
|
|
20
20
|
} from "./terminal-capabilities";
|
|
21
|
+
import { isInsideTmux, wrapTmuxPassthrough } from "./tmux";
|
|
21
22
|
import { type HangulCompatibilityJamoWidth, setHangulCompatibilityJamoWidth } from "./utils";
|
|
22
23
|
|
|
23
24
|
const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
|
|
@@ -456,6 +457,7 @@ function parseOsc99KeyValues(section: string): Map<string, string> {
|
|
|
456
457
|
return values;
|
|
457
458
|
}
|
|
458
459
|
const XTERM_SCROLL_TO_BOTTOM_MODES = [1010, 1011] as const;
|
|
460
|
+
type Osc11QueryRoute = "direct" | "tmux";
|
|
459
461
|
|
|
460
462
|
function isXtermScrollToBottomMode(mode: number): boolean {
|
|
461
463
|
return mode === 1010 || mode === 1011;
|
|
@@ -509,7 +511,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
509
511
|
#appearanceCallbacks: Array<(appearance: TerminalAppearance) => void> = [];
|
|
510
512
|
#appearance: TerminalAppearance | undefined;
|
|
511
513
|
#osc11Pending = false;
|
|
512
|
-
#
|
|
514
|
+
#osc11QueuedRoute?: Osc11QueryRoute;
|
|
513
515
|
#osc11ResponseBuffer = "";
|
|
514
516
|
#osc99PendingId: string | undefined;
|
|
515
517
|
#osc99ResponseBuffer = "";
|
|
@@ -572,15 +574,15 @@ export class ProcessTerminal implements Terminal {
|
|
|
572
574
|
|
|
573
575
|
/**
|
|
574
576
|
* Re-query the terminal background via a single OSC 11 probe. Reuses the
|
|
575
|
-
* startup
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
* Bounded to one probe per call; no timers are
|
|
579
|
-
* or after the terminal is torn down.
|
|
577
|
+
* startup DA1-sentinel FIFO, pending/queued gating, parsing, dedup, and
|
|
578
|
+
* appearance callbacks. Inside tmux, only this explicit path wraps the query
|
|
579
|
+
* and sentinel together for passthrough to the outer terminal; startup and
|
|
580
|
+
* Mode 2031 probes remain direct. Bounded to one probe per call; no timers are
|
|
581
|
+
* armed. Suppressed while headless or after the terminal is torn down.
|
|
580
582
|
*/
|
|
581
583
|
refreshAppearance(): void {
|
|
582
584
|
if (this.#headless || this.#dead) return;
|
|
583
|
-
this.#queryBackgroundColor();
|
|
585
|
+
this.#queryBackgroundColor(isInsideTmux() ? "tmux" : "direct");
|
|
584
586
|
}
|
|
585
587
|
|
|
586
588
|
onPrivateModeReport(callback: (mode: number, supported: boolean, confirmed?: boolean) => void): void {
|
|
@@ -917,13 +919,14 @@ export class ProcessTerminal implements Terminal {
|
|
|
917
919
|
}
|
|
918
920
|
// Start a queued OSC 11 query once the prior cycle is fully drained.
|
|
919
921
|
if (
|
|
920
|
-
this.#
|
|
922
|
+
this.#osc11QueuedRoute !== undefined &&
|
|
921
923
|
!this.#osc11Pending &&
|
|
922
924
|
!this.#da1SentinelOwners.some(o => o.kind === "osc11") &&
|
|
923
925
|
!this.#dead
|
|
924
926
|
) {
|
|
925
|
-
|
|
926
|
-
this.#
|
|
927
|
+
const route = this.#osc11QueuedRoute;
|
|
928
|
+
this.#osc11QueuedRoute = undefined;
|
|
929
|
+
this.#startOsc11Query(route);
|
|
927
930
|
}
|
|
928
931
|
break;
|
|
929
932
|
}
|
|
@@ -1058,23 +1061,28 @@ export class ProcessTerminal implements Terminal {
|
|
|
1058
1061
|
* DA1 avoids indefinite hangs: if DA1 response arrives before OSC 11,
|
|
1059
1062
|
* the terminal does not support OSC 11.
|
|
1060
1063
|
*/
|
|
1061
|
-
#queryBackgroundColor(): void {
|
|
1064
|
+
#queryBackgroundColor(route: Osc11QueryRoute = "direct"): void {
|
|
1062
1065
|
if (this.#dead) return;
|
|
1063
1066
|
// Queue if an OSC 11 query is in flight or its DA1 sentinel hasn't been
|
|
1064
1067
|
// consumed yet. Starting a new query while a DA1 is outstanding would
|
|
1065
1068
|
// increment the sentinel counter, and the old DA1 arrival would then
|
|
1066
|
-
// prematurely clear the new query's pending state.
|
|
1069
|
+
// prematurely clear the new query's pending state. Preserve a requested
|
|
1070
|
+
// tmux passthrough route when coalescing direct and explicit queries.
|
|
1067
1071
|
if (this.#osc11Pending || this.#da1SentinelOwners.some(o => o.kind === "osc11")) {
|
|
1068
|
-
this.#
|
|
1072
|
+
if (this.#osc11QueuedRoute !== "tmux") this.#osc11QueuedRoute = route;
|
|
1069
1073
|
return;
|
|
1070
1074
|
}
|
|
1071
|
-
this.#startOsc11Query();
|
|
1075
|
+
this.#startOsc11Query(route);
|
|
1072
1076
|
}
|
|
1073
1077
|
|
|
1074
|
-
#startOsc11Query(): void {
|
|
1078
|
+
#startOsc11Query(route: Osc11QueryRoute): void {
|
|
1075
1079
|
this.#osc11Pending = true;
|
|
1076
1080
|
this.#osc11ResponseBuffer = "";
|
|
1077
1081
|
this.#da1SentinelOwners.push({ kind: "osc11" });
|
|
1082
|
+
if (route === "tmux") {
|
|
1083
|
+
this.#safeWrite(wrapTmuxPassthrough("\x1b]11;?\x07\x1b[c"));
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1078
1086
|
this.#safeWrite("\x1b]11;?\x07"); // OSC 11 query (BEL terminated)
|
|
1079
1087
|
this.#safeWrite("\x1b[c"); // DA1 sentinel
|
|
1080
1088
|
}
|
|
@@ -1397,7 +1405,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
1397
1405
|
this.#appearanceCallbacks = [];
|
|
1398
1406
|
this.#osc11Pending = false;
|
|
1399
1407
|
this.#clearWindowsTerminalAppearancePoll();
|
|
1400
|
-
this.#
|
|
1408
|
+
this.#osc11QueuedRoute = undefined;
|
|
1401
1409
|
this.#osc11ResponseBuffer = "";
|
|
1402
1410
|
this.#osc99PendingId = undefined;
|
|
1403
1411
|
this.#osc99ResponseBuffer = "";
|