Skip to content

Commit 52f0207

Browse files
committed
fix: prevent duplicated logs when scrolling with multiline messages
1 parent 43aed55 commit 52f0207

File tree

8 files changed

+1622
-1666
lines changed

8 files changed

+1622
-1666
lines changed

packages/prompts/src/autocomplete.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -290,35 +290,44 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
290290
`${color.dim('Type:')} to search`,
291291
];
292292

293-
// No results message
294-
const noResults =
295-
this.filteredOptions.length === 0 && userInput
296-
? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`]
297-
: [];
298-
299-
const errorMessage =
300-
this.state === 'error' ? [`${color.cyan(S_BAR)} ${color.yellow(this.error)}`] : [];
301-
302-
// Get limited options for display
303-
const displayOptions = limitOptions({
304-
cursor: this.cursor,
305-
options: this.filteredOptions,
306-
style: (option, active) =>
307-
formatOption(option, active, this.selectedValues, this.focusedValue),
308-
maxItems: opts.maxItems,
309-
output: opts.output,
310-
});
311-
312-
// Build the prompt display
313-
return [
314-
title,
315-
`${color.cyan(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`,
316-
...noResults,
317-
...errorMessage,
318-
...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`),
319-
`${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`,
320-
`${color.cyan(S_BAR_END)}`,
321-
].join('\n');
293+
// No results message
294+
const noResults =
295+
this.filteredOptions.length === 0 && userInput
296+
? [`${color.cyan(S_BAR)} ${color.yellow('No matches found')}`]
297+
: [];
298+
299+
const errorMessage =
300+
this.state === 'error' ? [`${color.cyan(S_BAR)} ${color.yellow(this.error)}`] : [];
301+
302+
// Calculate header and footer line counts for rowPadding
303+
const headerLines = [
304+
...title.split('\n'),
305+
`${color.cyan(S_BAR)} ${color.dim('Search:')} ${searchText}${matches}`,
306+
...noResults,
307+
...errorMessage,
308+
];
309+
const footerLines = [
310+
`${color.cyan(S_BAR)} ${color.dim(instructions.join(' • '))}`,
311+
`${color.cyan(S_BAR_END)}`,
312+
];
313+
314+
// Get limited options for display
315+
const displayOptions = limitOptions({
316+
cursor: this.cursor,
317+
options: this.filteredOptions,
318+
style: (option, active) =>
319+
formatOption(option, active, this.selectedValues, this.focusedValue),
320+
maxItems: opts.maxItems,
321+
output: opts.output,
322+
rowPadding: headerLines.length + footerLines.length,
323+
});
324+
325+
// Build the prompt display
326+
return [
327+
...headerLines,
328+
...displayOptions.map((option) => `${color.cyan(S_BAR)} ${option}`),
329+
...footerLines,
330+
].join('\n');
322331
}
323332
}
324333
},

packages/prompts/src/multi-select.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -138,34 +138,42 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
138138
const wrappedLabel = wrapTextWithPrefix(opts.output, label, `${color.gray(S_BAR)} `);
139139
return `${title}${wrappedLabel}\n${color.gray(S_BAR)}`;
140140
}
141-
case 'error': {
142-
const prefix = `${color.yellow(S_BAR)} `;
143-
const footer = this.error
144-
.split('\n')
145-
.map((ln, i) =>
146-
i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}`
147-
)
148-
.join('\n');
149-
return `${title}${prefix}${limitOptions({
150-
output: opts.output,
151-
options: this.options,
152-
cursor: this.cursor,
153-
maxItems: opts.maxItems,
154-
columnPadding: prefix.length,
155-
style: styleOption,
156-
}).join(`\n${prefix}`)}\n${footer}\n`;
157-
}
158-
default: {
159-
const prefix = `${color.cyan(S_BAR)} `;
160-
return `${title}${prefix}${limitOptions({
161-
output: opts.output,
162-
options: this.options,
163-
cursor: this.cursor,
164-
maxItems: opts.maxItems,
165-
columnPadding: prefix.length,
166-
style: styleOption,
167-
}).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`;
168-
}
141+
case 'error': {
142+
const prefix = `${color.yellow(S_BAR)} `;
143+
const footer = this.error
144+
.split('\n')
145+
.map((ln, i) =>
146+
i === 0 ? `${color.yellow(S_BAR_END)} ${color.yellow(ln)}` : ` ${ln}`
147+
)
148+
.join('\n');
149+
// Calculate rowPadding: title lines + footer lines (error message + trailing newline)
150+
const titleLineCount = title.split('\n').length;
151+
const footerLineCount = footer.split('\n').length + 1; // footer + trailing newline
152+
return `${title}${prefix}${limitOptions({
153+
output: opts.output,
154+
options: this.options,
155+
cursor: this.cursor,
156+
maxItems: opts.maxItems,
157+
columnPadding: prefix.length,
158+
rowPadding: titleLineCount + footerLineCount,
159+
style: styleOption,
160+
}).join(`\n${prefix}`)}\n${footer}\n`;
161+
}
162+
default: {
163+
const prefix = `${color.cyan(S_BAR)} `;
164+
// Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline)
165+
const titleLineCount = title.split('\n').length;
166+
const footerLineCount = 2; // S_BAR_END + trailing newline
167+
return `${title}${prefix}${limitOptions({
168+
output: opts.output,
169+
options: this.options,
170+
cursor: this.cursor,
171+
maxItems: opts.maxItems,
172+
columnPadding: prefix.length,
173+
rowPadding: titleLineCount + footerLineCount,
174+
style: styleOption,
175+
}).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`;
176+
}
169177
}
170178
},
171179
}).prompt() as Promise<Value[] | symbol>;

packages/prompts/src/select.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,21 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
142142
);
143143
return `${title}${wrappedLines}\n${color.gray(S_BAR)}`;
144144
}
145-
default: {
146-
const prefix = `${color.cyan(S_BAR)} `;
147-
return `${title}${prefix}${limitOptions({
148-
output: opts.output,
149-
cursor: this.cursor,
150-
options: this.options,
151-
maxItems: opts.maxItems,
152-
columnPadding: prefix.length,
153-
style: (item, active) =>
154-
opt(item, item.disabled ? 'disabled' : active ? 'active' : 'inactive'),
155-
}).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`;
145+
default: {
146+
const prefix = `${color.cyan(S_BAR)} `;
147+
// Calculate rowPadding: title lines + footer lines (S_BAR_END + trailing newline)
148+
const titleLineCount = title.split('\n').length;
149+
const footerLineCount = 2; // S_BAR_END + trailing newline
150+
return `${title}${prefix}${limitOptions({
151+
output: opts.output,
152+
cursor: this.cursor,
153+
options: this.options,
154+
maxItems: opts.maxItems,
155+
columnPadding: prefix.length,
156+
rowPadding: titleLineCount + footerLineCount,
157+
style: (item, active) =>
158+
opt(item, item.disabled ? 'disabled' : active ? 'active' : 'inactive'),
159+
}).join(`\n${prefix}`)}\n${color.cyan(S_BAR_END)}\n`;
156160
}
157161
}
158162
},

0 commit comments

Comments
 (0)