@oh-my-pi/pi-tui 13.3.7 → 13.3.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 +11 -0
- package/package.json +6 -9
- package/src/components/markdown.ts +3 -3
- package/src/index.ts +1 -1
- package/src/mermaid.ts +5 -5
- package/src/terminal.ts +56 -1
- package/src/tui.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.3.8] - 2026-02-28
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Changed mermaid hash type from string to bigint in `getMermaidImage` callback and `extractMermaidBlocks` return type
|
|
9
|
+
- Removed `mime-types` and `@types/mime-types` from dependencies
|
|
10
|
+
- Removed `@xterm/xterm` from dependencies
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Updated mermaid hash computation to use `Bun.hash.xxHash64()` instead of `Bun.hash().toString(16)`
|
|
15
|
+
|
|
5
16
|
## [12.19.0] - 2026-02-22
|
|
6
17
|
|
|
7
18
|
### 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": "13.3.
|
|
4
|
+
"version": "13.3.8",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -33,16 +33,13 @@
|
|
|
33
33
|
"test": "bun test test/*.test.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@oh-my-pi/pi-natives": "13.3.
|
|
37
|
-
"@oh-my-pi/pi-utils": "13.3.
|
|
38
|
-
"
|
|
39
|
-
"chalk": "^5.6",
|
|
40
|
-
"marked": "^17.0",
|
|
41
|
-
"mime-types": "^3.0"
|
|
36
|
+
"@oh-my-pi/pi-natives": "13.3.8",
|
|
37
|
+
"@oh-my-pi/pi-utils": "13.3.8",
|
|
38
|
+
"marked": "^17.0"
|
|
42
39
|
},
|
|
43
40
|
"devDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"@xterm/
|
|
41
|
+
"chalk": "^5.6",
|
|
42
|
+
"@xterm/headless": "^6.0"
|
|
46
43
|
},
|
|
47
44
|
"engines": {
|
|
48
45
|
"bun": ">=1.3.7"
|
|
@@ -46,10 +46,10 @@ export interface MarkdownTheme {
|
|
|
46
46
|
highlightCode?: (code: string, lang?: string) => string[];
|
|
47
47
|
/**
|
|
48
48
|
* Lookup a pre-rendered mermaid image by source hash.
|
|
49
|
-
* Hash is computed as `Bun.hash(source.trim())
|
|
49
|
+
* Hash is computed as `Bun.hash.xxHash64(source.trim())`.
|
|
50
50
|
* Return null to fall back to text rendering.
|
|
51
51
|
*/
|
|
52
|
-
getMermaidImage?: (sourceHash:
|
|
52
|
+
getMermaidImage?: (sourceHash: bigint) => MermaidImage | null;
|
|
53
53
|
symbols: SymbolTheme;
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -322,7 +322,7 @@ export class Markdown implements Component {
|
|
|
322
322
|
case "code": {
|
|
323
323
|
// Handle mermaid diagrams with image rendering when available
|
|
324
324
|
if (token.lang === "mermaid" && this.#theme.getMermaidImage) {
|
|
325
|
-
const hash = Bun.hash(token.text.trim())
|
|
325
|
+
const hash = Bun.hash.xxHash64(token.text.trim());
|
|
326
326
|
const image = this.#theme.getMermaidImage(hash);
|
|
327
327
|
|
|
328
328
|
if (image && TERMINAL.imageProtocol) {
|
package/src/index.ts
CHANGED
|
@@ -57,7 +57,7 @@ export {
|
|
|
57
57
|
export { StdinBuffer, type StdinBufferEventMap, type StdinBufferOptions } from "./stdin-buffer";
|
|
58
58
|
export type { BoxSymbols, SymbolTheme } from "./symbols";
|
|
59
59
|
// Terminal interface and implementations
|
|
60
|
-
export { emergencyTerminalRestore, ProcessTerminal, type Terminal } from "./terminal";
|
|
60
|
+
export { emergencyTerminalRestore, ProcessTerminal, type Terminal, type TerminalAppearance } from "./terminal";
|
|
61
61
|
// Terminal image support
|
|
62
62
|
export * from "./terminal-capabilities";
|
|
63
63
|
// TTY ID
|
package/src/mermaid.ts
CHANGED
|
@@ -99,13 +99,13 @@ function parsePngDimensions(buffer: Uint8Array): { width: number; height: number
|
|
|
99
99
|
* Extract mermaid code blocks from markdown text.
|
|
100
100
|
* Returns array of { source, startIndex, endIndex } for each block.
|
|
101
101
|
*/
|
|
102
|
-
export function extractMermaidBlocks(markdown: string): { source: string; hash:
|
|
103
|
-
const blocks: { source: string; hash:
|
|
102
|
+
export function extractMermaidBlocks(markdown: string): { source: string; hash: bigint }[] {
|
|
103
|
+
const blocks: { source: string; hash: bigint }[] = [];
|
|
104
104
|
const regex = /```mermaid\s*\n([\s\S]*?)```/g;
|
|
105
105
|
|
|
106
106
|
for (let match = regex.exec(markdown); match !== null; match = regex.exec(markdown)) {
|
|
107
107
|
const source = match[1].trim();
|
|
108
|
-
const hash = Bun.hash(source)
|
|
108
|
+
const hash = Bun.hash.xxHash64(source);
|
|
109
109
|
blocks.push({ source, hash });
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -119,9 +119,9 @@ export function extractMermaidBlocks(markdown: string): { source: string; hash:
|
|
|
119
119
|
export async function prerenderMermaidBlocks(
|
|
120
120
|
markdown: string,
|
|
121
121
|
options: MermaidRenderOptions = {},
|
|
122
|
-
): Promise<Map<
|
|
122
|
+
): Promise<Map<bigint, MermaidImage>> {
|
|
123
123
|
const blocks = extractMermaidBlocks(markdown);
|
|
124
|
-
const cache = new Map<
|
|
124
|
+
const cache = new Map<bigint, MermaidImage>();
|
|
125
125
|
|
|
126
126
|
const results = await Promise.all(
|
|
127
127
|
blocks.map(async ({ source, hash }) => {
|
package/src/terminal.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function emergencyTerminalRestore(): void {
|
|
|
30
30
|
// This avoids writing escape sequences for non-TUI commands (grep, commit, etc.)
|
|
31
31
|
process.stdout.write(
|
|
32
32
|
"\x1b[?2004l" + // Disable bracketed paste
|
|
33
|
+
"\x1b[?2031l" + // Disable Mode 2031 appearance notifications
|
|
33
34
|
"\x1b[<u" + // Pop kitty keyboard protocol
|
|
34
35
|
"\x1b[?25h", // Show cursor
|
|
35
36
|
);
|
|
@@ -41,6 +42,8 @@ export function emergencyTerminalRestore(): void {
|
|
|
41
42
|
// Terminal may already be dead during crash cleanup - ignore errors
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
/** Terminal-reported appearance (dark/light mode). */
|
|
46
|
+
export type TerminalAppearance = "dark" | "light";
|
|
44
47
|
export interface Terminal {
|
|
45
48
|
// Start the terminal with input and resize handlers
|
|
46
49
|
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
@@ -80,6 +83,16 @@ export interface Terminal {
|
|
|
80
83
|
|
|
81
84
|
// Title operations
|
|
82
85
|
setTitle(title: string): void; // Set terminal window title
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register a callback for Mode 2031 dark/light appearance change notifications.
|
|
89
|
+
* Supported by Ghostty, Kitty, Contour, VTE (GNOME Terminal), and tmux 3.6+.
|
|
90
|
+
* The callback fires when the terminal reports a change; it does NOT fire for the initial query.
|
|
91
|
+
*/
|
|
92
|
+
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void;
|
|
93
|
+
|
|
94
|
+
/** The last appearance reported by the terminal, or undefined if not yet known. */
|
|
95
|
+
get appearance(): TerminalAppearance | undefined;
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
/**
|
|
@@ -95,11 +108,21 @@ export class ProcessTerminal implements Terminal {
|
|
|
95
108
|
#dead = false;
|
|
96
109
|
#writeLogPath = $env.PI_TUI_WRITE_LOG || "";
|
|
97
110
|
#windowsVTInputRestore?: () => void;
|
|
111
|
+
#appearanceCallbacks: Array<(appearance: TerminalAppearance) => void> = [];
|
|
112
|
+
#appearance: TerminalAppearance | undefined;
|
|
98
113
|
|
|
99
114
|
get kittyProtocolActive(): boolean {
|
|
100
115
|
return this.#kittyProtocolActive;
|
|
101
116
|
}
|
|
102
117
|
|
|
118
|
+
get appearance(): TerminalAppearance | undefined {
|
|
119
|
+
return this.#appearance;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
onAppearanceChange(callback: (appearance: TerminalAppearance) => void): void {
|
|
123
|
+
this.#appearanceCallbacks.push(callback);
|
|
124
|
+
}
|
|
125
|
+
|
|
103
126
|
start(onInput: (data: string) => void, onResize: () => void): void {
|
|
104
127
|
this.#inputHandler = onInput;
|
|
105
128
|
this.#resizeHandler = onResize;
|
|
@@ -137,6 +160,13 @@ export class ProcessTerminal implements Terminal {
|
|
|
137
160
|
// The query handler intercepts input temporarily, then installs the user's handler
|
|
138
161
|
// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
|
139
162
|
this.#queryAndEnableKittyProtocol();
|
|
163
|
+
|
|
164
|
+
// Enable Mode 2031: terminal will send DSR notifications on dark/light appearance changes.
|
|
165
|
+
// Query current mode with CSI ? 996 n, then subscribe with CSI ? 2031 h.
|
|
166
|
+
// Supported by Ghostty, Kitty, Contour, VTE/GNOME Terminal, tmux 3.6+.
|
|
167
|
+
// Unsupported terminals silently ignore these sequences.
|
|
168
|
+
this.#safeWrite("\x1b[?996n"); // Query current appearance
|
|
169
|
+
this.#safeWrite("\x1b[?2031h"); // Subscribe to appearance changes
|
|
140
170
|
}
|
|
141
171
|
|
|
142
172
|
/**
|
|
@@ -203,6 +233,9 @@ export class ProcessTerminal implements Terminal {
|
|
|
203
233
|
// Kitty protocol response pattern: \x1b[?<flags>u
|
|
204
234
|
const kittyResponsePattern = /^\x1b\[\?(\d+)u$/;
|
|
205
235
|
|
|
236
|
+
// Mode 2031 DSR response pattern: \x1b[?997;{1=dark,2=light}n
|
|
237
|
+
const appearanceDsrPattern = /^\x1b\[\?997;([12])n$/;
|
|
238
|
+
|
|
206
239
|
// Forward individual sequences to the input handler
|
|
207
240
|
this.#stdinBuffer.on("data", (sequence: string) => {
|
|
208
241
|
// Check for Kitty protocol response (only if not already enabled)
|
|
@@ -221,6 +254,23 @@ export class ProcessTerminal implements Terminal {
|
|
|
221
254
|
}
|
|
222
255
|
}
|
|
223
256
|
|
|
257
|
+
// Check for Mode 2031 appearance DSR response: CSI ? 997 ; {1,2} n
|
|
258
|
+
const appearanceMatch = sequence.match(appearanceDsrPattern);
|
|
259
|
+
if (appearanceMatch) {
|
|
260
|
+
const mode: TerminalAppearance = appearanceMatch[1] === "1" ? "dark" : "light";
|
|
261
|
+
const changed = mode !== this.#appearance;
|
|
262
|
+
this.#appearance = mode;
|
|
263
|
+
if (changed) {
|
|
264
|
+
for (const cb of this.#appearanceCallbacks) {
|
|
265
|
+
try {
|
|
266
|
+
cb(mode);
|
|
267
|
+
} catch {
|
|
268
|
+
/* ignore callback errors */
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return; // Don't forward DSR to TUI
|
|
273
|
+
}
|
|
224
274
|
if (this.#inputHandler) {
|
|
225
275
|
this.#inputHandler(sequence);
|
|
226
276
|
}
|
|
@@ -297,6 +347,10 @@ export class ProcessTerminal implements Terminal {
|
|
|
297
347
|
// Disable bracketed paste mode
|
|
298
348
|
this.#safeWrite("\x1b[?2004l");
|
|
299
349
|
|
|
350
|
+
// Disable Mode 2031 appearance change notifications
|
|
351
|
+
this.#safeWrite("\x1b[?2031l");
|
|
352
|
+
this.#appearanceCallbacks = [];
|
|
353
|
+
|
|
300
354
|
// Disable Kitty keyboard protocol if not already done by drainInput()
|
|
301
355
|
if (this.#kittyProtocolActive) {
|
|
302
356
|
this.#safeWrite("\x1b[<u");
|
|
@@ -317,6 +371,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
317
371
|
this.#stdinDataHandler = undefined;
|
|
318
372
|
}
|
|
319
373
|
this.#inputHandler = undefined;
|
|
374
|
+
this.#appearance = undefined;
|
|
320
375
|
if (this.#resizeHandler) {
|
|
321
376
|
process.stdout.removeListener("resize", this.#resizeHandler);
|
|
322
377
|
this.#resizeHandler = undefined;
|
|
@@ -391,7 +446,7 @@ export class ProcessTerminal implements Terminal {
|
|
|
391
446
|
}
|
|
392
447
|
|
|
393
448
|
clearScreen(): void {
|
|
394
|
-
this.#safeWrite("\x1b[
|
|
449
|
+
this.#safeWrite("\x1b[H\x1b[0J"); // Move to home (1,1) and clear from cursor to end
|
|
395
450
|
}
|
|
396
451
|
|
|
397
452
|
setTitle(title: string): void {
|
package/src/tui.ts
CHANGED
|
@@ -886,7 +886,7 @@ export class TUI extends Container {
|
|
|
886
886
|
const fullRender = (clear: boolean): void => {
|
|
887
887
|
this.#fullRedrawCount += 1;
|
|
888
888
|
let buffer = "\x1b[?2026h"; // Begin synchronized output
|
|
889
|
-
if (clear) buffer += this.#clearScrollbackOnNextFullRender ? "\x1b[3J\x1b[
|
|
889
|
+
if (clear) buffer += this.#clearScrollbackOnNextFullRender ? "\x1b[3J\x1b[H\x1b[0J" : "\x1b[H\x1b[0J"; // Home + clear from cursor (and optionally scrollback)
|
|
890
890
|
for (let i = 0; i < newLines.length; i++) {
|
|
891
891
|
if (i > 0) buffer += "\r\n";
|
|
892
892
|
buffer += newLines[i];
|