Skip to content

Commit 282b39e

Browse files
authored
fix: wrap spinner output (#359)
1 parent d4de920 commit 282b39e

File tree

7 files changed

+341
-139
lines changed

7 files changed

+341
-139
lines changed

.changeset/dull-singers-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clack/prompts": patch
3+
---
4+
5+
Wrap spinner output to allow for multi-line/wrapped messages.

packages/prompts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"is-unicode-supported": "^1.3.0",
6262
"memfs": "^4.17.2",
6363
"vitest": "^3.2.4",
64-
"vitest-ansi-serializer": "^0.1.2"
64+
"vitest-ansi-serializer": "^0.1.2",
65+
"wrap-ansi": "^8.1.0"
6566
}
6667
}

packages/prompts/src/spinner.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { block, settings } from '@clack/core';
1+
import { block, getColumns, settings } from '@clack/core';
22
import color from 'picocolors';
33
import { cursor, erase } from 'sisteransi';
4+
import wrap from 'wrap-ansi';
45
import {
56
type CommonOptions,
67
isCI as isCIFn,
@@ -46,6 +47,7 @@ export const spinner = ({
4647
let _message = '';
4748
let _prevMessage: string | undefined;
4849
let _origin: number = performance.now();
50+
const columns = getColumns(output);
4951

5052
const handleExit = (code: number) => {
5153
const msg =
@@ -94,9 +96,14 @@ export const spinner = ({
9496
const clearPrevMessage = () => {
9597
if (_prevMessage === undefined) return;
9698
if (isCI) output.write('\n');
97-
const prevLines = _prevMessage.split('\n');
98-
output.write(cursor.move(-999, prevLines.length - 1));
99-
output.write(erase.down(prevLines.length));
99+
const wrapped = wrap(_prevMessage, columns, {
100+
hard: true,
101+
trim: false,
102+
});
103+
const prevLines = wrapped.split('\n');
104+
output.write(cursor.up(prevLines.length - 1));
105+
output.write(cursor.to(0));
106+
output.write(erase.down());
100107
};
101108

102109
const removeTrailingDots = (msg: string): string => {
@@ -126,16 +133,23 @@ export const spinner = ({
126133
clearPrevMessage();
127134
_prevMessage = _message;
128135
const frame = color.magenta(frames[frameIndex]);
136+
let outputMessage: string;
129137

130138
if (isCI) {
131-
output.write(`${frame} ${_message}...`);
139+
outputMessage = `${frame} ${_message}...`;
132140
} else if (indicator === 'timer') {
133-
output.write(`${frame} ${_message} ${formatTimer(_origin)}`);
141+
outputMessage = `${frame} ${_message} ${formatTimer(_origin)}`;
134142
} else {
135143
const loadingDots = '.'.repeat(Math.floor(indicatorTimer)).slice(0, 3);
136-
output.write(`${frame} ${_message}${loadingDots}`);
144+
outputMessage = `${frame} ${_message}${loadingDots}`;
137145
}
138146

147+
const wrapped = wrap(outputMessage, columns, {
148+
hard: true,
149+
trim: false,
150+
});
151+
output.write(wrapped);
152+
139153
frameIndex = frameIndex + 1 < frames.length ? frameIndex + 1 : 0;
140154
// indicator increase by 1 every 8 frames
141155
indicatorTimer = indicatorTimer < 4 ? indicatorTimer + 0.125 : 0;

packages/prompts/test/__snapshots__/progress-bar.test.ts.snap

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ exports[`prompts - progress (isCI = false) > message > sets message for next fra
66
"│
77
",
88
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
9-
"<cursor.backward count=999>",
9+
"<cursor.up count=0>",
10+
"<cursor.left count=1>",
1011
"<erase.down>",
1112
"◐ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo",
1213
]
@@ -95,13 +96,16 @@ exports[`prompts - progress (isCI = false) > start > renders frames at interval
9596
"│
9697
",
9798
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
98-
"<cursor.backward count=999>",
99+
"<cursor.up count=0>",
100+
"<cursor.left count=1>",
99101
"<erase.down>",
100102
"◐ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
101-
"<cursor.backward count=999>",
103+
"<cursor.up count=0>",
104+
"<cursor.left count=1>",
102105
"<erase.down>",
103106
"◓ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
104-
"<cursor.backward count=999>",
107+
"<cursor.up count=0>",
108+
"<cursor.left count=1>",
105109
"<erase.down>",
106110
"◑ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
107111
]
@@ -131,7 +135,8 @@ exports[`prompts - progress (isCI = false) > stop > renders cancel symbol if cod
131135
"│
132136
",
133137
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
134-
"<cursor.backward count=999>",
138+
"<cursor.up count=0>",
139+
"<cursor.left count=1>",
135140
"<erase.down>",
136141
"■
137142
",
@@ -145,7 +150,8 @@ exports[`prompts - progress (isCI = false) > stop > renders error symbol if code
145150
"│
146151
",
147152
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
148-
"<cursor.backward count=999>",
153+
"<cursor.up count=0>",
154+
"<cursor.left count=1>",
149155
"<erase.down>",
150156
"▲
151157
",
@@ -159,7 +165,8 @@ exports[`prompts - progress (isCI = false) > stop > renders message 1`] = `
159165
"│
160166
",
161167
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
162-
"<cursor.backward count=999>",
168+
"<cursor.up count=0>",
169+
"<cursor.left count=1>",
163170
"<erase.down>",
164171
"◇ foo
165172
",
@@ -173,7 +180,8 @@ exports[`prompts - progress (isCI = false) > stop > renders message without remo
173180
"│
174181
",
175182
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
176-
"<cursor.backward count=999>",
183+
"<cursor.up count=0>",
184+
"<cursor.left count=1>",
177185
"<erase.down>",
178186
"◇ foo.
179187
",
@@ -187,7 +195,8 @@ exports[`prompts - progress (isCI = false) > stop > renders submit symbol and st
187195
"│
188196
",
189197
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ",
190-
"<cursor.backward count=999>",
198+
"<cursor.up count=0>",
199+
"<cursor.left count=1>",
191200
"<erase.down>",
192201
"◇
193202
",
@@ -201,16 +210,20 @@ exports[`prompts - progress (isCI = false) > style > renders block progressbar 1
201210
"│
202211
",
203212
"◒ ██████████ ",
204-
"<cursor.backward count=999>",
213+
"<cursor.up count=0>",
214+
"<cursor.left count=1>",
205215
"<erase.down>",
206216
"◐ ██████████ ",
207-
"<cursor.backward count=999>",
217+
"<cursor.up count=0>",
218+
"<cursor.left count=1>",
208219
"<erase.down>",
209220
"◓ ██████████ ",
210-
"<cursor.backward count=999>",
221+
"<cursor.up count=0>",
222+
"<cursor.left count=1>",
211223
"<erase.down>",
212224
"◑ ██████████ ",
213-
"<cursor.backward count=999>",
225+
"<cursor.up count=0>",
226+
"<cursor.left count=1>",
214227
"<erase.down>",
215228
"◇
216229
",
@@ -224,16 +237,20 @@ exports[`prompts - progress (isCI = false) > style > renders heavy progressbar 1
224237
"│
225238
",
226239
"◒ ━━━━━━━━━━ ",
227-
"<cursor.backward count=999>",
240+
"<cursor.up count=0>",
241+
"<cursor.left count=1>",
228242
"<erase.down>",
229243
"◐ ━━━━━━━━━━ ",
230-
"<cursor.backward count=999>",
244+
"<cursor.up count=0>",
245+
"<cursor.left count=1>",
231246
"<erase.down>",
232247
"◓ ━━━━━━━━━━ ",
233-
"<cursor.backward count=999>",
248+
"<cursor.up count=0>",
249+
"<cursor.left count=1>",
234250
"<erase.down>",
235251
"◑ ━━━━━━━━━━ ",
236-
"<cursor.backward count=999>",
252+
"<cursor.up count=0>",
253+
"<cursor.left count=1>",
237254
"<erase.down>",
238255
"◇
239256
",
@@ -247,16 +264,20 @@ exports[`prompts - progress (isCI = false) > style > renders light progressbar 1
247264
"│
248265
",
249266
"◒ ────────── ",
250-
"<cursor.backward count=999>",
267+
"<cursor.up count=0>",
268+
"<cursor.left count=1>",
251269
"<erase.down>",
252270
"◐ ────────── ",
253-
"<cursor.backward count=999>",
271+
"<cursor.up count=0>",
272+
"<cursor.left count=1>",
254273
"<erase.down>",
255274
"◓ ────────── ",
256-
"<cursor.backward count=999>",
275+
"<cursor.up count=0>",
276+
"<cursor.left count=1>",
257277
"<erase.down>",
258278
"◑ ────────── ",
259-
"<cursor.backward count=999>",
279+
"<cursor.up count=0>",
280+
"<cursor.left count=1>",
260281
"<erase.down>",
261282
"◇
262283
",
@@ -272,7 +293,8 @@ exports[`prompts - progress (isCI = true) > message > sets message for next fram
272293
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
273294
"
274295
",
275-
"<cursor.backward count=999>",
296+
"<cursor.up count=0>",
297+
"<cursor.left count=1>",
276298
"<erase.down>",
277299
"◐ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ foo...",
278300
]
@@ -390,7 +412,8 @@ exports[`prompts - progress (isCI = true) > stop > renders cancel symbol if code
390412
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
391413
"
392414
",
393-
"<cursor.backward count=999>",
415+
"<cursor.up count=0>",
416+
"<cursor.left count=1>",
394417
"<erase.down>",
395418
"■
396419
",
@@ -406,7 +429,8 @@ exports[`prompts - progress (isCI = true) > stop > renders error symbol if code
406429
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
407430
"
408431
",
409-
"<cursor.backward count=999>",
432+
"<cursor.up count=0>",
433+
"<cursor.left count=1>",
410434
"<erase.down>",
411435
"▲
412436
",
@@ -422,7 +446,8 @@ exports[`prompts - progress (isCI = true) > stop > renders message 1`] = `
422446
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
423447
"
424448
",
425-
"<cursor.backward count=999>",
449+
"<cursor.up count=0>",
450+
"<cursor.left count=1>",
426451
"<erase.down>",
427452
"◇ foo
428453
",
@@ -438,7 +463,8 @@ exports[`prompts - progress (isCI = true) > stop > renders message without remov
438463
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
439464
"
440465
",
441-
"<cursor.backward count=999>",
466+
"<cursor.up count=0>",
467+
"<cursor.left count=1>",
442468
"<erase.down>",
443469
"◇ foo.
444470
",
@@ -454,7 +480,8 @@ exports[`prompts - progress (isCI = true) > stop > renders submit symbol and sto
454480
"◒ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ...",
455481
"
456482
",
457-
"<cursor.backward count=999>",
483+
"<cursor.up count=0>",
484+
"<cursor.left count=1>",
458485
"<erase.down>",
459486
"◇
460487
",
@@ -470,12 +497,14 @@ exports[`prompts - progress (isCI = true) > style > renders block progressbar 1`
470497
"◒ ██████████ ...",
471498
"
472499
",
473-
"<cursor.backward count=999>",
500+
"<cursor.up count=0>",
501+
"<cursor.left count=1>",
474502
"<erase.down>",
475503
"◐ ██████████ ...",
476504
"
477505
",
478-
"<cursor.backward count=999>",
506+
"<cursor.up count=0>",
507+
"<cursor.left count=1>",
479508
"<erase.down>",
480509
"◇
481510
",
@@ -491,12 +520,14 @@ exports[`prompts - progress (isCI = true) > style > renders heavy progressbar 1`
491520
"◒ ━━━━━━━━━━ ...",
492521
"
493522
",
494-
"<cursor.backward count=999>",
523+
"<cursor.up count=0>",
524+
"<cursor.left count=1>",
495525
"<erase.down>",
496526
"◐ ━━━━━━━━━━ ...",
497527
"
498528
",
499-
"<cursor.backward count=999>",
529+
"<cursor.up count=0>",
530+
"<cursor.left count=1>",
500531
"<erase.down>",
501532
"◇
502533
",
@@ -512,12 +543,14 @@ exports[`prompts - progress (isCI = true) > style > renders light progressbar 1`
512543
"◒ ────────── ...",
513544
"
514545
",
515-
"<cursor.backward count=999>",
546+
"<cursor.up count=0>",
547+
"<cursor.left count=1>",
516548
"<erase.down>",
517549
"◐ ────────── ...",
518550
"
519551
",
520-
"<cursor.backward count=999>",
552+
"<cursor.up count=0>",
553+
"<cursor.left count=1>",
521554
"<erase.down>",
522555
"◇
523556
",

0 commit comments

Comments
 (0)