Skip to content

Commit 9053625

Browse files
authored
fix: account for escape sequences in prefixes (#631)
1 parent e66ab12 commit 9053625

11 files changed

Lines changed: 243 additions & 262 deletions

File tree

‎.changeset/eight-lights-hug.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clack/prompts": patch
3+
"@clack/core": patch
4+
---
5+
6+
Account for escape sequences in prefixes during line wrapping. Very narrow outputs will now render correctly.

‎packages/core/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"test": "vitest run"
5656
},
5757
"dependencies": {
58+
"fast-string-width": "^3.0.2",
5859
"fast-wrap-ansi": "^0.2.0",
5960
"sisteransi": "^2.0.0"
6061
},

‎packages/core/src/utils/index.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Key } from 'node:readline';
33
import * as readline from 'node:readline';
44
import type { Readable, Writable } from 'node:stream';
55
import { ReadStream } from 'node:tty';
6+
import stringWidth from 'fast-string-width';
67
import { wrapAnsi } from 'fast-wrap-ansi';
78
import { cursor } from 'sisteransi';
89
import { isActionKey } from './settings.js';
@@ -107,7 +108,8 @@ export function wrapTextWithPrefix(
107108
lineFormatter?: (line: string, index: number) => string
108109
): string {
109110
const columns = getColumns(output ?? stdout);
110-
const wrapped = wrapAnsi(text, columns - prefix.length, {
111+
const prefixWidth = stringWidth(prefix);
112+
const wrapped = wrapAnsi(text, columns - prefixWidth, {
111113
hard: true,
112114
trim: false,
113115
});

‎packages/prompts/src/common.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Readable, Writable } from 'node:stream';
22
import { styleText } from 'node:util';
33
import type { State } from '@clack/core';
4+
import { wrapTextWithPrefix } from '@clack/core';
45
import isUnicodeSupported from 'is-unicode-supported';
56

67
export const unicode = isUnicodeSupported();
@@ -77,9 +78,13 @@ export interface CommonOptions {
7778
accessible?: boolean;
7879
}
7980

80-
export function formatInstructionFooter(instructions: string[], hasGuide: boolean): string[] {
81+
export function formatInstructionFooter(
82+
output: Writable | undefined,
83+
instructions: string[],
84+
hasGuide: boolean
85+
): string[] {
8186
const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
82-
const footerLines = [`${guidePrefix}${instructions.join(' • ')}`];
87+
const footerLines = wrapTextWithPrefix(output, instructions.join(' • '), guidePrefix).split('\n');
8388
if (hasGuide) {
8489
footerLines.push(styleText('cyan', S_BAR_END));
8590
}

‎packages/prompts/src/group-multi-select.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
297297
const guidePrefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
298298
const titleLineCount = title.split('\n').length;
299299
const footerLines = showInstructions
300-
? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide)
300+
? formatInstructionFooter(opts.output, MULTISELECT_INSTRUCTIONS, hasGuide)
301301
: hasGuide
302302
? [styleText('cyan', S_BAR_END)]
303303
: [];

‎packages/prompts/src/multi-select.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
187187
const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
188188
const titleLineCount = title.split('\n').length;
189189
const footerLines = showInstructions
190-
? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, hasGuide)
190+
? formatInstructionFooter(opts.output, MULTISELECT_INSTRUCTIONS, hasGuide)
191191
: hasGuide
192192
? [styleText('cyan', S_BAR_END)]
193193
: [];

‎packages/prompts/src/select.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
164164
const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
165165
const titleLineCount = title.split('\n').length;
166166
const footerLines = showInstructions
167-
? formatInstructionFooter(SELECT_INSTRUCTIONS, hasGuide)
167+
? formatInstructionFooter(opts.output, SELECT_INSTRUCTIONS, hasGuide)
168168
: hasGuide
169169
? [styleText('cyan', S_BAR_END)]
170170
: [];

‎packages/prompts/test/__snapshots__/multi-select.test.ts.snap‎

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ exports[`multiselect (isCI = false) > wraps cancelled state with long options 1`
872872
│ 1 Option 1 Option 1 Option 
873873
│ 1 Option 1
874874
│ ↑/↓ to navigate • Space: select •
875-
[2mEnter:[22m confirm
875+
[36m│[39m [2mEnter:[22m confirm
876876
└
877877
",
878878
"<cursor.backward count=999><cursor.up count=13>",
@@ -884,10 +884,9 @@ exports[`multiselect (isCI = false) > wraps cancelled state with long options 1`
884884
"<cursor.down count=1>",
885885
"<erase.down>",
886886
"■ foo
887-
│ Option 0 Option 0 Option 0 
888-
│ Option 0 Option 0 Option 0 
889-
│ Option 0 Option 0 Option 0 
890-
│ Option 0
887+
│ Option 0 Option 0 Option 0 Option 0 
888+
│ Option 0 Option 0 Option 0 Option 0 
889+
│ Option 0 Option 0
891890
│",
892891
"
893892
",
@@ -899,13 +898,13 @@ exports[`multiselect (isCI = false) > wraps long messages 1`] = `
899898
[
900899
"<cursor.hide>",
901900
"│
902-
[36m◆[39m foo foo foo foo foo foo foo
903-
[36m│[39m foo foo foo foo foo foo
904-
[36m│[39m foo foo foo foo foo foo foo
901+
[36m◆[39m foo foo foo foo foo foo foo foo foo
902+
[36m│[39m foo foo foo foo foo foo foo foo foo
903+
[36m│[39m foo foo
905904
│ ◻ opt0
906905
│ ◻ opt1
907906
│ ↑/↓ to navigate • Space: select •
908-
[2mEnter:[22m confirm
907+
[36m│[39m [2mEnter:[22m confirm
909908
└
910909
",
911910
"<cursor.backward count=999><cursor.up count=9>",
@@ -916,9 +915,9 @@ exports[`multiselect (isCI = false) > wraps long messages 1`] = `
916915
"<cursor.backward count=999><cursor.up count=9>",
917916
"<cursor.down count=1>",
918917
"<erase.down>",
919-
"[32m◇[39m foo foo foo foo foo foo foo
920-
[32m│[39m foo foo foo foo foo foo
921-
[32m│[39m foo foo foo foo foo foo foo
918+
"[32m◇[39m foo foo foo foo foo foo foo foo foo
919+
[32m│[39m foo foo foo foo foo foo foo foo foo
920+
[32m│[39m foo foo
922921
│ opt0",
923922
"
924923
",
@@ -940,7 +939,7 @@ exports[`multiselect (isCI = false) > wraps success state with long options 1`]
940939
│ 1 Option 1 Option 1 Option 
941940
│ 1 Option 1
942941
│ ↑/↓ to navigate • Space: select •
943-
[2mEnter:[22m confirm
942+
[36m│[39m [2mEnter:[22m confirm
944943
└
945944
",
946945
"<cursor.backward count=999><cursor.up count=13>",
@@ -952,10 +951,9 @@ exports[`multiselect (isCI = false) > wraps success state with long options 1`]
952951
"<cursor.down count=1>",
953952
"<erase.down>",
954953
"◇ foo
955-
│ Option 0 Option 0 Option 0 
956-
│ Option 0 Option 0 Option 0 
957-
│ Option 0 Option 0 Option 0 
958-
│ Option 0",
954+
│ Option 0 Option 0 Option 0 Option 0 
955+
│ Option 0 Option 0 Option 0 Option 0 
956+
│ Option 0 Option 0",
959957
"
960958
",
961959
"<cursor.show>",
@@ -1834,7 +1832,7 @@ exports[`multiselect (isCI = true) > wraps cancelled state with long options 1`]
18341832
│ 1 Option 1 Option 1 Option 
18351833
│ 1 Option 1
18361834
│ ↑/↓ to navigate • Space: select •
1837-
[2mEnter:[22m confirm
1835+
[36m│[39m [2mEnter:[22m confirm
18381836
└
18391837
",
18401838
"<cursor.backward count=999><cursor.up count=13>",
@@ -1846,10 +1844,9 @@ exports[`multiselect (isCI = true) > wraps cancelled state with long options 1`]
18461844
"<cursor.down count=1>",
18471845
"<erase.down>",
18481846
"■ foo
1849-
│ Option 0 Option 0 Option 0 
1850-
│ Option 0 Option 0 Option 0 
1851-
│ Option 0 Option 0 Option 0 
1852-
│ Option 0
1847+
│ Option 0 Option 0 Option 0 Option 0 
1848+
│ Option 0 Option 0 Option 0 Option 0 
1849+
│ Option 0 Option 0
18531850
│",
18541851
"
18551852
",
@@ -1861,13 +1858,13 @@ exports[`multiselect (isCI = true) > wraps long messages 1`] = `
18611858
[
18621859
"<cursor.hide>",
18631860
"│
1864-
[36m◆[39m foo foo foo foo foo foo foo
1865-
[36m│[39m foo foo foo foo foo foo
1866-
[36m│[39m foo foo foo foo foo foo foo
1861+
[36m◆[39m foo foo foo foo foo foo foo foo foo
1862+
[36m│[39m foo foo foo foo foo foo foo foo foo
1863+
[36m│[39m foo foo
18671864
│ ◻ opt0
18681865
│ ◻ opt1
18691866
│ ↑/↓ to navigate • Space: select •
1870-
[2mEnter:[22m confirm
1867+
[36m│[39m [2mEnter:[22m confirm
18711868
└
18721869
",
18731870
"<cursor.backward count=999><cursor.up count=9>",
@@ -1878,9 +1875,9 @@ exports[`multiselect (isCI = true) > wraps long messages 1`] = `
18781875
"<cursor.backward count=999><cursor.up count=9>",
18791876
"<cursor.down count=1>",
18801877
"<erase.down>",
1881-
"[32m◇[39m foo foo foo foo foo foo foo
1882-
[32m│[39m foo foo foo foo foo foo
1883-
[32m│[39m foo foo foo foo foo foo foo
1878+
"[32m◇[39m foo foo foo foo foo foo foo foo foo
1879+
[32m│[39m foo foo foo foo foo foo foo foo foo
1880+
[32m│[39m foo foo
18841881
│ opt0",
18851882
"
18861883
",
@@ -1902,7 +1899,7 @@ exports[`multiselect (isCI = true) > wraps success state with long options 1`] =
19021899
│ 1 Option 1 Option 1 Option 
19031900
│ 1 Option 1
19041901
│ ↑/↓ to navigate • Space: select •
1905-
[2mEnter:[22m confirm
1902+
[36m│[39m [2mEnter:[22m confirm
19061903
└
19071904
",
19081905
"<cursor.backward count=999><cursor.up count=13>",
@@ -1914,10 +1911,9 @@ exports[`multiselect (isCI = true) > wraps success state with long options 1`] =
19141911
"<cursor.down count=1>",
19151912
"<erase.down>",
19161913
"◇ foo
1917-
│ Option 0 Option 0 Option 0 
1918-
│ Option 0 Option 0 Option 0 
1919-
│ Option 0 Option 0 Option 0 
1920-
│ Option 0",
1914+
│ Option 0 Option 0 Option 0 Option 0 
1915+
│ Option 0 Option 0 Option 0 Option 0 
1916+
│ Option 0 Option 0",
19211917
"
19221918
",
19231919
"<cursor.show>",

0 commit comments

Comments
 (0)