Skip to content

Commit 0971edd

Browse files
committed
Respect withGuide: false in autocomplete and multiselect prompts
1 parent 417b451 commit 0971edd

10 files changed

Lines changed: 475 additions & 36 deletions

‎.changeset/tangy-meals-allow.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+
Respect `withGuide: false` in autocomplete and multiselect prompts.

‎packages/prompts/src/autocomplete.ts‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,11 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
285285
input: opts.input,
286286
output: opts.output,
287287
render() {
288+
const hasGuide = opts.withGuide ?? settings.withGuide;
288289
// Title and symbol
289-
const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
290+
const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${
291+
opts.message
292+
}\n`;
290293

291294
// Selection counter
292295
const userInput = this.userInput;
@@ -312,13 +315,21 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
312315
// Render prompt state
313316
switch (this.state) {
314317
case 'submit': {
315-
return `${title}${styleText('gray', S_BAR)} ${styleText('dim', `${this.selectedValues.length} items selected`)}`;
318+
return `${title}${hasGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText(
319+
'dim',
320+
`${this.selectedValues.length} items selected`
321+
)}`;
316322
}
317323
case 'cancel': {
318-
return `${title}${styleText('gray', S_BAR)} ${styleText(['strikethrough', 'dim'], userInput)}`;
324+
return `${title}${hasGuide ? `${styleText('gray', S_BAR)} ` : ''}${styleText(
325+
['strikethrough', 'dim'],
326+
userInput
327+
)}`;
319328
}
320329
default: {
321330
const barStyle = this.state === 'error' ? 'yellow' : 'cyan';
331+
const guidePrefix = hasGuide ? `${styleText(barStyle, S_BAR)} ` : '';
332+
const guidePrefixEnd = hasGuide ? styleText(barStyle, S_BAR_END) : '';
322333
// Instructions
323334
const instructions = [
324335
`${styleText('dim', '↑/↓')} to navigate`,
@@ -330,25 +341,20 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
330341
// No results message
331342
const noResults =
332343
this.filteredOptions.length === 0 && userInput
333-
? [`${styleText(barStyle, S_BAR)} ${styleText('yellow', 'No matches found')}`]
344+
? [`${guidePrefix}${styleText('yellow', 'No matches found')}`]
334345
: [];
335346

336347
const errorMessage =
337-
this.state === 'error'
338-
? [`${styleText(barStyle, S_BAR)} ${styleText('yellow', this.error)}`]
339-
: [];
348+
this.state === 'error' ? [`${guidePrefix}${styleText('yellow', this.error)}`] : [];
340349

341350
// Calculate header and footer line counts for rowPadding
342351
const headerLines = [
343-
...`${title}${styleText(barStyle, S_BAR)}`.split('\n'),
344-
`${styleText(barStyle, S_BAR)} ${styleText('dim', 'Search:')} ${searchText}${matches}`,
352+
...`${title}${hasGuide ? styleText(barStyle, S_BAR) : ''}`.split('\n'),
353+
`${guidePrefix}${styleText('dim', 'Search:')} ${searchText}${matches}`,
345354
...noResults,
346355
...errorMessage,
347356
];
348-
const footerLines = [
349-
`${styleText(barStyle, S_BAR)} ${instructions.join(' • ')}`,
350-
styleText(barStyle, S_BAR_END),
351-
];
357+
const footerLines = [`${guidePrefix}${instructions.join(' • ')}`, guidePrefixEnd];
352358

353359
// Get limited options for display
354360
const displayOptions = limitOptions({
@@ -364,7 +370,7 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
364370
// Build the prompt display
365371
return [
366372
...headerLines,
367-
...displayOptions.map((option) => `${styleText(barStyle, S_BAR)} ${option}`),
373+
...displayOptions.map((option) => `${guidePrefix}${option}`),
368374
...footerLines,
369375
].join('\n');
370376
}

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { styleText } from 'node:util';
2-
import { GroupMultiSelectPrompt } from '@clack/core';
2+
import { GroupMultiSelectPrompt, settings } from '@clack/core';
33
import {
44
type CommonOptions,
55
S_BAR,
@@ -104,7 +104,8 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
104104
)}`;
105105
},
106106
render() {
107-
const title = `${styleText('gray', S_BAR)}\n${symbol(this.state)} ${opts.message}\n`;
107+
const hasGuide = opts.withGuide ?? settings.withGuide;
108+
const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${symbol(this.state)} ${opts.message}\n`;
108109
const value = this.value ?? [];
109110

110111
switch (this.state) {
@@ -114,25 +115,27 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
114115
.map((option) => opt(option, 'submitted'));
115116
const optionsText =
116117
selectedOptions.length === 0 ? '' : ` ${selectedOptions.join(styleText('dim', ', '))}`;
117-
return `${title}${styleText('gray', S_BAR)}${optionsText}`;
118+
return `${title}${hasGuide ? styleText('gray', S_BAR) : ''}${optionsText}`;
118119
}
119120
case 'cancel': {
120121
const label = this.options
121122
.filter(({ value: optionValue }) => value.includes(optionValue))
122123
.map((option) => opt(option, 'cancelled'))
123124
.join(styleText('dim', ', '));
124-
return `${title}${styleText('gray', S_BAR)} ${
125-
label.trim() ? `${label}\n${styleText('gray', S_BAR)}` : ''
125+
return `${title}${hasGuide ? `${styleText('gray', S_BAR)} ` : ''}${
126+
label.trim() ? `${label}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}` : ''
126127
}`;
127128
}
128129
case 'error': {
129130
const footer = this.error
130131
.split('\n')
131132
.map((ln, i) =>
132-
i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}`
133+
i === 0
134+
? `${hasGuide ? `${styleText('yellow', S_BAR_END)} ` : ''}${styleText('yellow', ln)}`
135+
: ` ${ln}`
133136
)
134137
.join('\n');
135-
return `${title}${styleText('yellow', S_BAR)} ${this.options
138+
return `${title}${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}${this.options
136139
.map((option, i, options) => {
137140
const selected =
138141
value.includes(option.value) ||
@@ -153,7 +156,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
153156
}
154157
return opt(option, active ? 'active' : 'inactive', options);
155158
})
156-
.join(`\n${styleText('yellow', S_BAR)} `)}\n${footer}\n`;
159+
.join(`\n${hasGuide ? `${styleText('yellow', S_BAR)} ` : ''}`)}\n${footer}\n`;
157160
}
158161
default: {
159162
const optionsText = this.options
@@ -183,9 +186,11 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
183186
const prefix = i !== 0 && !optionText.startsWith('\n') ? ' ' : '';
184187
return `${prefix}${optionText}`;
185188
})
186-
.join(`\n${styleText('cyan', S_BAR)}`);
189+
.join(`\n${hasGuide ? styleText('cyan', S_BAR) : ''}`);
187190
const optionsPrefix = optionsText.startsWith('\n') ? '' : ' ';
188-
return `${title}${styleText('cyan', S_BAR)}${optionsPrefix}${optionsText}\n${styleText('cyan', S_BAR_END)}\n`;
191+
return `${title}${hasGuide ? styleText('cyan', S_BAR) : ''}${optionsPrefix}${optionsText}\n${
192+
hasGuide ? styleText('cyan', S_BAR_END) : ''
193+
}\n`;
189194
}
190195
}
191196
},

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

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { styleText } from 'node:util';
2-
import { MultiSelectPrompt, wrapTextWithPrefix } from '@clack/core';
2+
import { MultiSelectPrompt, settings, wrapTextWithPrefix } from '@clack/core';
33
import {
44
type CommonOptions,
55
S_BAR,
@@ -93,13 +93,14 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
9393
)}`;
9494
},
9595
render() {
96+
const hasGuide = opts.withGuide ?? settings.withGuide;
9697
const wrappedMessage = wrapTextWithPrefix(
9798
opts.output,
9899
opts.message,
99-
`${symbolBar(this.state)} `,
100+
hasGuide ? `${symbolBar(this.state)} ` : '',
100101
`${symbol(this.state)} `
101102
);
102-
const title = `${styleText('gray', S_BAR)}\n${wrappedMessage}\n`;
103+
const title = `${hasGuide ? `${styleText('gray', S_BAR)}\n` : ''}${wrappedMessage}\n`;
103104
const value = this.value ?? [];
104105

105106
const styleOption = (option: Option<Value>, active: boolean) => {
@@ -126,7 +127,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
126127
const wrappedSubmitText = wrapTextWithPrefix(
127128
opts.output,
128129
submitText,
129-
`${styleText('gray', S_BAR)} `
130+
hasGuide ? `${styleText('gray', S_BAR)} ` : ''
130131
);
131132
return `${title}${wrappedSubmitText}`;
132133
}
@@ -141,16 +142,18 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
141142
const wrappedLabel = wrapTextWithPrefix(
142143
opts.output,
143144
label,
144-
`${styleText('gray', S_BAR)} `
145+
hasGuide ? `${styleText('gray', S_BAR)} ` : ''
145146
);
146-
return `${title}${wrappedLabel}\n${styleText('gray', S_BAR)}`;
147+
return `${title}${wrappedLabel}${hasGuide ? `\n${styleText('gray', S_BAR)}` : ''}`;
147148
}
148149
case 'error': {
149-
const prefix = `${styleText('yellow', S_BAR)} `;
150+
const prefix = hasGuide ? `${styleText('yellow', S_BAR)} ` : '';
150151
const footer = this.error
151152
.split('\n')
152153
.map((ln, i) =>
153-
i === 0 ? `${styleText('yellow', S_BAR_END)} ${styleText('yellow', ln)}` : ` ${ln}`
154+
i === 0
155+
? `${hasGuide ? `${styleText('yellow', S_BAR_END)} ` : ''}${styleText('yellow', ln)}`
156+
: ` ${ln}`
154157
)
155158
.join('\n');
156159
// Calculate rowPadding: title lines + footer lines (error message + trailing newline)
@@ -167,10 +170,10 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
167170
}).join(`\n${prefix}`)}\n${footer}\n`;
168171
}
169172
default: {
170-
const prefix = `${styleText('cyan', S_BAR)} `;
173+
const prefix = hasGuide ? `${styleText('cyan', S_BAR)} ` : '';
171174
// Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline)
172175
const titleLineCount = title.split('\n').length;
173-
const footerLineCount = 2; // S_BAR_END + trailing newline
176+
const footerLineCount = hasGuide ? 2 : 1; // S_BAR_END + trailing newline
174177
return `${title}${prefix}${limitOptions({
175178
output: opts.output,
176179
options: this.options,
@@ -179,7 +182,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
179182
columnPadding: prefix.length,
180183
rowPadding: titleLineCount + footerLineCount,
181184
style: styleOption,
182-
}).join(`\n${prefix}`)}\n${styleText('cyan', S_BAR_END)}\n`;
185+
}).join(`\n${prefix}`)}\n${hasGuide ? styleText('cyan', S_BAR_END) : ''}\n`;
183186
}
184187
}
185188
},

‎packages/prompts/test/__snapshots__/autocomplete.test.ts.snap‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,83 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`autocomplete > autocompleteMultiselect respects global withGuide: false 1`] = `
4+
[
5+
"<cursor.hide>",
6+
"◆ Select fruits
7+
8+
Search: _
9+
◻ Apple
10+
◻ Banana
11+
◻ Cherry
12+
◻ Grape
13+
◻ Orange
14+
↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search
15+
",
16+
"<cursor.backward count=999><cursor.up count=9>",
17+
"<cursor.down count=2>",
18+
"<erase.down>",
19+
"Search: 
20+
◻ Apple
21+
◻ Banana
22+
◻ Cherry
23+
◻ Grape
24+
◻ Orange
25+
↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search
26+
",
27+
"<cursor.backward count=999><cursor.up count=9>",
28+
"<cursor.down count=4>",
29+
"<erase.line><cursor.left count=1>",
30+
"◼ Banana",
31+
"<cursor.down count=5>",
32+
"<cursor.backward count=999><cursor.up count=9>",
33+
"<erase.down>",
34+
"◇ Select fruits
35+
1 items selected",
36+
"
37+
",
38+
"<cursor.show>",
39+
]
40+
`;
41+
42+
exports[`autocomplete > autocompleteMultiselect respects withGuide: false 1`] = `
43+
[
44+
"<cursor.hide>",
45+
"◆ Select fruits
46+
47+
Search: _
48+
◻ Apple
49+
◻ Banana
50+
◻ Cherry
51+
◻ Grape
52+
◻ Orange
53+
↑/↓ to navigate • Tab: select • Enter: confirm • Type: to search
54+
",
55+
"<cursor.backward count=999><cursor.up count=9>",
56+
"<cursor.down count=2>",
57+
"<erase.down>",
58+
"Search: 
59+
◻ Apple
60+
◻ Banana
61+
◻ Cherry
62+
◻ Grape
63+
◻ Orange
64+
↑/↓ to navigate • Space/Tab: select • Enter: confirm • Type: to search
65+
",
66+
"<cursor.backward count=999><cursor.up count=9>",
67+
"<cursor.down count=4>",
68+
"<erase.line><cursor.left count=1>",
69+
"◼ Banana",
70+
"<cursor.down count=5>",
71+
"<cursor.backward count=999><cursor.up count=9>",
72+
"<erase.down>",
73+
"◇ Select fruits
74+
1 items selected",
75+
"
76+
",
77+
"<cursor.show>",
78+
]
79+
`;
80+
381
exports[`autocomplete > can be aborted by a signal 1`] = `
482
[
583
"<cursor.hide>",

0 commit comments

Comments
 (0)