Skip to content

Commit b7e52b6

Browse files
committed
fix spinner message duplication on narrow terminals
1 parent 33a2783 commit b7e52b6

File tree

1 file changed

+62
-1
lines changed

1 file changed

+62
-1
lines changed

packages/cli-v3/src/utilities/windows.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,67 @@ export function escapeImportPath(path: string) {
77
return isWindows ? path.replaceAll("\\", "\\\\") : path;
88
}
99

10+
// Removes ANSI escape sequences to get actual visible length
11+
function getVisibleLength(str: string): number {
12+
return (
13+
str
14+
// Remove terminal hyperlinks: \u001b]8;;URL\u0007TEXT\u001b]8;;\u0007
15+
.replace(/\u001b]8;;[^\u0007]*\u0007/g, "")
16+
// Remove standard ANSI escape sequences (colors, cursor movement, etc.)
17+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length
18+
);
19+
}
20+
21+
function truncateMessage(msg: string, maxLength?: number): string {
22+
const terminalWidth = maxLength ?? process.stdout.columns ?? 80;
23+
const availableWidth = terminalWidth - 5; // Reserve some space for the spinner and padding
24+
const visibleLength = getVisibleLength(msg);
25+
26+
if (visibleLength <= availableWidth) {
27+
return msg;
28+
}
29+
30+
// We need to truncate based on visible characters, but preserve ANSI sequences
31+
// Simple approach: truncate character by character until we fit
32+
let truncated = msg;
33+
while (getVisibleLength(truncated) > availableWidth - 3) {
34+
truncated = truncated.slice(0, -1);
35+
}
36+
37+
return truncated + "...";
38+
}
39+
40+
const wrappedClackSpinner = () => {
41+
let currentMessage = "";
42+
let isActive = false;
43+
44+
const handleResize = () => {
45+
if (isActive && currentMessage) {
46+
spinner.message(truncateMessage(currentMessage));
47+
}
48+
};
49+
50+
const spinner = clackSpinner();
51+
52+
return {
53+
start: (msg?: string): void => {
54+
currentMessage = msg ?? "";
55+
isActive = true;
56+
process.stdout.on("resize", handleResize);
57+
spinner.start(truncateMessage(currentMessage));
58+
},
59+
stop: (msg?: string, code?: number): void => {
60+
isActive = false;
61+
process.stdout.off("resize", handleResize);
62+
spinner.stop(truncateMessage(msg ?? ""), code);
63+
},
64+
message: (msg?: string): void => {
65+
currentMessage = msg ?? "";
66+
spinner.message(truncateMessage(currentMessage));
67+
},
68+
};
69+
};
70+
1071
const ballmerSpinner = () => ({
1172
start: (msg?: string): void => {
1273
log.step(msg ?? "");
@@ -21,4 +82,4 @@ const ballmerSpinner = () => ({
2182

2283
// This will become unecessary with the next clack release, the bug was fixed here:
2384
// https://github.com/natemoo-re/clack/pull/182
24-
export const spinner = () => (isWindows ? ballmerSpinner() : clackSpinner());
85+
export const spinner = () => (isWindows ? ballmerSpinner() : wrappedClackSpinner());

0 commit comments

Comments
 (0)