@heyhuynhgiabuu/pi-pretty 0.4.3 → 0.4.4
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/package.json +1 -1
- package/release-notes/v0.4.4.md +28 -0
- package/src/index.ts +7 -28
- package/test/bash-rendering.test.ts +58 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heyhuynhgiabuu/pi-pretty",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Pretty terminal output for pi — syntax-highlighted file reads, colored bash output, tree-view directory listings, and more.",
|
|
5
5
|
"author": "huynhgiabuu",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# pi-pretty 0.4.4
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
This patch release fixes terminal background tearing/misaligned cells by enforcing the pi-tui line-width contract for background-filled tool output.
|
|
5
|
+
|
|
6
|
+
## What changed
|
|
7
|
+
- Clamp every background-filled tool line to the active terminal width before adding background padding.
|
|
8
|
+
- Use `@mariozechner/pi-tui` `truncateToWidth()` and `visibleWidth()` so ANSI escapes and wide Unicode cells are measured correctly.
|
|
9
|
+
- Preserve the configured tool background after ANSI reset sequences even when truncation occurs.
|
|
10
|
+
- Remove the 80-column minimum width floor so narrow terminals do not receive over-wide rendered lines.
|
|
11
|
+
- Replace custom read-output truncation with pi-tui's ANSI-aware truncation helper.
|
|
12
|
+
- Add regression coverage for expanded ANSI tool headers, wide characters, and narrow terminal widths.
|
|
13
|
+
|
|
14
|
+
## Files
|
|
15
|
+
- `src/index.ts`
|
|
16
|
+
- `test/bash-rendering.test.ts`
|
|
17
|
+
- `package.json`
|
|
18
|
+
- `package-lock.json`
|
|
19
|
+
|
|
20
|
+
## Verification
|
|
21
|
+
- `npm run lint` ✅
|
|
22
|
+
- `npm run typecheck` ✅
|
|
23
|
+
- `npm test` ✅ (69 tests)
|
|
24
|
+
|
|
25
|
+
## Upgrade notes
|
|
26
|
+
No configuration changes are required.
|
|
27
|
+
|
|
28
|
+
This release specifically follows the pi-tui requirement that rendered component lines must not exceed the available width, preventing terminal auto-wrap artifacts in red/error and themed tool backgrounds.
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type {
|
|
|
41
41
|
ReadToolInput,
|
|
42
42
|
ToolRenderResultOptions,
|
|
43
43
|
} from "@mariozechner/pi-coding-agent";
|
|
44
|
+
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
44
45
|
import { codeToANSI } from "@shikijs/cli";
|
|
45
46
|
import type { BundledLanguage, BundledTheme } from "shiki";
|
|
46
47
|
|
|
@@ -148,7 +149,6 @@ function renderToolError(error: string, theme: FgTheme): string {
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
const ESC_RE = "\u001b";
|
|
151
|
-
const ANSI_RE = new RegExp(`${ESC_RE}\\[[0-9;]*m`, "g");
|
|
152
152
|
const ANSI_CAPTURE_RE = new RegExp(`${ESC_RE}\\[([0-9;]*)m`, "g");
|
|
153
153
|
|
|
154
154
|
// ---------------------------------------------------------------------------
|
|
@@ -174,10 +174,6 @@ function normalizeShikiContrast(ansi: string): string {
|
|
|
174
174
|
// Utilities
|
|
175
175
|
// ---------------------------------------------------------------------------
|
|
176
176
|
|
|
177
|
-
function strip(s: string): string {
|
|
178
|
-
return s.replace(ANSI_RE, "");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
177
|
function normalizeLineEndings(text: string): string {
|
|
182
178
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
183
179
|
}
|
|
@@ -195,8 +191,9 @@ function fillToolBackground(text: string, bg = BG_BASE): string {
|
|
|
195
191
|
.split("\n")
|
|
196
192
|
.map((line) => {
|
|
197
193
|
const normalized = preserveToolBackground(line, bg);
|
|
198
|
-
const
|
|
199
|
-
|
|
194
|
+
const fitted = preserveToolBackground(truncateToWidth(normalized, width, ""), bg);
|
|
195
|
+
const padding = Math.max(0, width - visibleWidth(fitted));
|
|
196
|
+
return `${bg}${fitted}${" ".repeat(padding)}${RST}`;
|
|
200
197
|
})
|
|
201
198
|
.join("\n");
|
|
202
199
|
}
|
|
@@ -205,7 +202,7 @@ function termW(): number {
|
|
|
205
202
|
const stderrWithColumns = process.stderr as NodeJS.WriteStream & { columns?: number };
|
|
206
203
|
const raw =
|
|
207
204
|
process.stdout.columns || stderrWithColumns.columns || Number.parseInt(process.env.COLUMNS ?? "", 10) || 200;
|
|
208
|
-
return Math.max(
|
|
205
|
+
return Math.max(1, Math.min(raw - 4, 210));
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
function shortPath(cwd: string, home: string, p: string): string {
|
|
@@ -621,7 +618,7 @@ async function renderFileContent(
|
|
|
621
618
|
const endLine = startLine + show.length - 1;
|
|
622
619
|
const nw = Math.max(3, String(endLine).length);
|
|
623
620
|
const gw = nw + 3; // num + " │ "
|
|
624
|
-
const cw = Math.max(
|
|
621
|
+
const cw = Math.max(1, tw - gw);
|
|
625
622
|
|
|
626
623
|
const out: string[] = [];
|
|
627
624
|
out.push(rule(tw));
|
|
@@ -629,25 +626,7 @@ async function renderFileContent(
|
|
|
629
626
|
for (let i = 0; i < hl.length; i++) {
|
|
630
627
|
const ln = startLine + i;
|
|
631
628
|
const code = hl[i] ?? show[i] ?? "";
|
|
632
|
-
const
|
|
633
|
-
// Truncate if wider than available
|
|
634
|
-
let display = code;
|
|
635
|
-
if (plain.length > cw) {
|
|
636
|
-
let vis = 0;
|
|
637
|
-
let j = 0;
|
|
638
|
-
while (j < code.length && vis < cw - 1) {
|
|
639
|
-
if (code[j] === "\x1b") {
|
|
640
|
-
const e = code.indexOf("m", j);
|
|
641
|
-
if (e !== -1) {
|
|
642
|
-
j = e + 1;
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
vis++;
|
|
647
|
-
j++;
|
|
648
|
-
}
|
|
649
|
-
display = `${code.slice(0, j)}${RST}${FG_DIM}›${RST}`;
|
|
650
|
-
}
|
|
629
|
+
const display = truncateToWidth(code, cw, `${FG_DIM}›`);
|
|
651
630
|
out.push(`${lnum(ln, nw)} ${FG_RULE}│${RST} ${display}${RST}`);
|
|
652
631
|
}
|
|
653
632
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { visibleWidth } from "@mariozechner/pi-tui";
|
|
1
2
|
import { describe, expect, it } from "vitest";
|
|
2
3
|
|
|
3
4
|
import piPrettyExtension from "../src/index.js";
|
|
@@ -18,6 +19,11 @@ const mockTheme = {
|
|
|
18
19
|
bold: (text: string) => text,
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
const ansiMockTheme = {
|
|
23
|
+
fg: (_key: string, text: string) => `\x1b[31m${text}\x1b[0m`,
|
|
24
|
+
bold: (text: string) => `\x1b[1m${text}\x1b[22m`,
|
|
25
|
+
};
|
|
26
|
+
|
|
21
27
|
function mockToolFactory(exec: any) {
|
|
22
28
|
return (_cwd: string) => ({
|
|
23
29
|
name: "mock",
|
|
@@ -27,6 +33,20 @@ function mockToolFactory(exec: any) {
|
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
function withStdoutColumns<T>(columns: number, fn: () => T): T {
|
|
37
|
+
const descriptor = Object.getOwnPropertyDescriptor(process.stdout, "columns");
|
|
38
|
+
Object.defineProperty(process.stdout, "columns", { configurable: true, value: columns });
|
|
39
|
+
try {
|
|
40
|
+
return fn();
|
|
41
|
+
} finally {
|
|
42
|
+
if (descriptor) {
|
|
43
|
+
Object.defineProperty(process.stdout, "columns", descriptor);
|
|
44
|
+
} else {
|
|
45
|
+
delete (process.stdout as NodeJS.WriteStream & { columns?: number }).columns;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
function loadBashTool() {
|
|
31
51
|
const noopExec = async () => ({ content: [{ type: "text", text: "" }] });
|
|
32
52
|
const tools = new Map<string, any>();
|
|
@@ -106,4 +126,42 @@ describe("bash renderCall expansion", () => {
|
|
|
106
126
|
expect(collapsed.getText()).toContain("5s timeout");
|
|
107
127
|
expect(expanded.getText()).toContain("5s timeout");
|
|
108
128
|
});
|
|
129
|
+
|
|
130
|
+
it("truncates expanded ANSI tool headers to the terminal width before padding backgrounds", () => {
|
|
131
|
+
withStdoutColumns(84, () => {
|
|
132
|
+
const bashTool = loadBashTool();
|
|
133
|
+
const command = `printf '${"界".repeat(120)}'`;
|
|
134
|
+
|
|
135
|
+
const rendered = bashTool.renderCall({ command }, ansiMockTheme, {
|
|
136
|
+
lastComponent: new MockText(),
|
|
137
|
+
isError: false,
|
|
138
|
+
state: {},
|
|
139
|
+
expanded: true,
|
|
140
|
+
invalidate: () => {},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
for (const line of rendered.getText().split("\n")) {
|
|
144
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(80);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("does not exceed narrow terminal widths", () => {
|
|
150
|
+
withStdoutColumns(24, () => {
|
|
151
|
+
const bashTool = loadBashTool();
|
|
152
|
+
const command = `printf '${"x".repeat(120)}'`;
|
|
153
|
+
|
|
154
|
+
const rendered = bashTool.renderCall({ command }, ansiMockTheme, {
|
|
155
|
+
lastComponent: new MockText(),
|
|
156
|
+
isError: false,
|
|
157
|
+
state: {},
|
|
158
|
+
expanded: true,
|
|
159
|
+
invalidate: () => {},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
for (const line of rendered.getText().split("\n")) {
|
|
163
|
+
expect(visibleWidth(line)).toBeLessThanOrEqual(20);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
109
167
|
});
|