Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-select-prompt-clear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@effect/cli": patch
---

Fix `Prompt.select` rendering issue when choice descriptions cause terminal line wrapping.

The `handleClear` function now correctly calculates the number of terminal rows to erase by accounting for the actual rendered content length of each choice, including descriptions that are only shown for the selected item.
19 changes: 16 additions & 3 deletions packages/cli/src/internal/prompt/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,25 @@ function handleRender<A>(options: SelectOptions<A>) {
}
}

export function handleClear<A>(options: SelectOptions<A>) {
export function handleClear<A>(options: SelectOptions<A>, state: State) {
return Effect.gen(function*() {
const terminal = yield* Terminal.Terminal
const columns = yield* terminal.columns
const clearPrompt = Doc.cat(Doc.eraseLine, Doc.cursorLeft)
const text = "\n".repeat(Math.min(options.choices.length, options.maxPerPage)) + options.message
// Calculate actual content length per displayed choice to account for line wrapping
const toDisplay = entriesToDisplay(state, options.choices.length, options.maxPerPage)
const choiceLines: Array<string> = []
for (let i = toDisplay.startIndex; i < toDisplay.endIndex; i++) {
const choice = options.choices[i]
const isSelected = state === i
// Approximate rendered line: prefix (2 chars) + title + description (only shown when selected)
let line = " " + choice.title
if (isSelected && choice.description) {
line += " - " + choice.description
}
choiceLines.push(line)
}
const text = choiceLines.join("\n") + "\n" + options.message
const clearOutput = InternalAnsiUtils.eraseText(text, columns)
return clearOutput.pipe(
Doc.cat(clearPrompt),
Expand Down Expand Up @@ -243,6 +256,6 @@ export const select = <const A>(options: Prompt.Prompt.SelectOptions<A>): Prompt
return InternalPrompt.custom(initialIndex, {
render: handleRender(opts),
process: handleProcess(opts),
clear: () => handleClear(opts)
clear: (state) => handleClear(opts, state)
})
}