From fca11fbe9846fc22f7fdb8186349f472d371664d Mon Sep 17 00:00:00 2001 From: kingstarfly Date: Wed, 14 Jan 2026 11:46:21 +0800 Subject: [PATCH] fix(cli): fix Prompt.select clear when descriptions wrap --- .changeset/fix-select-prompt-clear.md | 7 +++++++ packages/cli/src/internal/prompt/select.ts | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-select-prompt-clear.md diff --git a/.changeset/fix-select-prompt-clear.md b/.changeset/fix-select-prompt-clear.md new file mode 100644 index 00000000000..dcc347f97d9 --- /dev/null +++ b/.changeset/fix-select-prompt-clear.md @@ -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. diff --git a/packages/cli/src/internal/prompt/select.ts b/packages/cli/src/internal/prompt/select.ts index beb4e3942dc..bae66d32e58 100644 --- a/packages/cli/src/internal/prompt/select.ts +++ b/packages/cli/src/internal/prompt/select.ts @@ -175,12 +175,25 @@ function handleRender(options: SelectOptions) { } } -export function handleClear(options: SelectOptions) { +export function handleClear(options: SelectOptions, 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 = [] + 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), @@ -243,6 +256,6 @@ export const select = (options: Prompt.Prompt.SelectOptions): Prompt return InternalPrompt.custom(initialIndex, { render: handleRender(opts), process: handleProcess(opts), - clear: () => handleClear(opts) + clear: (state) => handleClear(opts, state) }) }