diff --git a/.changeset/quiet-outputs-wrap.md b/.changeset/quiet-outputs-wrap.md new file mode 100644 index 00000000..cdb7a52d --- /dev/null +++ b/.changeset/quiet-outputs-wrap.md @@ -0,0 +1,5 @@ +--- +'@clack/core': patch +--- + +Use the configured output stream's width when wrapping and clearing prompt frames, including prompts written to stderr while stdout is redirected. diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index d1c61961..a468a460 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -8,6 +8,7 @@ import type { Action } from '../utils/index.js'; import { CANCEL_SYMBOL, diffLines, + getColumns, getRows, isAccessible, isActionKey, @@ -303,13 +304,13 @@ export default class Prompt { private restoreCursor() { const lines = - wrapAnsi(this._prevFrame, process.stdout.columns, { hard: true, trim: false }).split('\n') + wrapAnsi(this._prevFrame, getColumns(this.output), { hard: true, trim: false }).split('\n') .length - 1; this.output.write(cursor.move(-999, lines * -1)); } private render() { - const frame = wrapAnsi(this._render(this) ?? '', process.stdout.columns, { + const frame = wrapAnsi(this._render(this) ?? '', getColumns(this.output), { hard: true, trim: false, }); diff --git a/packages/core/test/prompts/prompt.test.ts b/packages/core/test/prompts/prompt.test.ts index 50c1ccda..a6952d0a 100644 --- a/packages/core/test/prompts/prompt.test.ts +++ b/packages/core/test/prompts/prompt.test.ts @@ -1,5 +1,5 @@ import { type } from 'arktype'; -import { cursor } from 'sisteransi'; +import { cursor, erase } from 'sisteransi'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { default as Prompt } from '../../src/prompts/prompt.js'; import { isCancel, settings } from '../../src/utils/index.js'; @@ -91,6 +91,33 @@ describe('Prompt', () => { expect(renderFn).toHaveBeenCalledTimes(2); }); + test.each([ + { columns: 4, text: 'abcdefgh', wrapped: 'abcd\nefgh' }, + { columns: 7, text: 'abcdefghijklmnop', wrapped: 'abcdefg\nhijklmn\nop' }, + ])( + 'wraps and clears frames using the output width ($columns columns)', + async ({ columns, text, wrapped }) => { + Object.assign(output, { columns }); + const render = vi.fn(() => text); + const instance = new Prompt({ input, output, render }); + const result = instance.prompt(); + + expect(output.buffer).to.deep.equal([cursor.hide, wrapped]); + + output.buffer = []; + render.mockReturnValue('x'); + output.emit('resize'); + + expect(output.buffer).to.deep.equal([ + cursor.move(-999, -(wrapped.split('\n').length - 1)), + erase.down(), + 'x', + ]); + input.emit('keypress', '\x03', { name: 'c' }); + await result; + } + ); + test('state is active after first render', async () => { const instance = new Prompt({ input, diff --git a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap index ae07ee85..950cfbfd 100644 --- a/packages/prompts/test/__snapshots__/multi-select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/multi-select.test.ts.snap @@ -871,15 +871,16 @@ exports[`multiselect (isCI = false) > wraps cancelled state with long options 1` │ 1 Option 1 Option 1 Option  │ 1 Option 1 Option 1 Option  │ 1 Option 1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ Option 0 Option 0 Option ", - "", - "", + "", + "", "", "", "■ foo @@ -903,15 +904,16 @@ exports[`multiselect (isCI = false) > wraps long messages 1`] = ` │ foo foo foo foo foo foo foo │ ◻ opt0 │ ◻ opt1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ opt0", - "", - "", + "", + "", "", "", "◇ foo foo foo foo foo foo foo @@ -937,15 +939,16 @@ exports[`multiselect (isCI = false) > wraps success state with long options 1`] │ 1 Option 1 Option 1 Option  │ 1 Option 1 Option 1 Option  │ 1 Option 1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ Option 0 Option 0 Option ", - "", - "", + "", + "", "", "", "◇ foo @@ -1830,15 +1833,16 @@ exports[`multiselect (isCI = true) > wraps cancelled state with long options 1`] │ 1 Option 1 Option 1 Option  │ 1 Option 1 Option 1 Option  │ 1 Option 1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ Option 0 Option 0 Option ", - "", - "", + "", + "", "", "", "■ foo @@ -1862,15 +1866,16 @@ exports[`multiselect (isCI = true) > wraps long messages 1`] = ` │ foo foo foo foo foo foo foo │ ◻ opt0 │ ◻ opt1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ opt0", - "", - "", + "", + "", "", "", "◇ foo foo foo foo foo foo foo @@ -1896,15 +1901,16 @@ exports[`multiselect (isCI = true) > wraps success state with long options 1`] = │ 1 Option 1 Option 1 Option  │ 1 Option 1 Option 1 Option  │ 1 Option 1 -│ ↑/↓ to navigate • Space: select • Enter: confirm +│ ↑/↓ to navigate • Space: select • +Enter: confirm └ ", - "", + "", "", "", "│ ◼ Option 0 Option 0 Option ", - "", - "", + "", + "", "", "", "◇ foo diff --git a/packages/prompts/test/__snapshots__/select.test.ts.snap b/packages/prompts/test/__snapshots__/select.test.ts.snap index 5a1b34b1..f7ceb1c1 100644 --- a/packages/prompts/test/__snapshots__/select.test.ts.snap +++ b/packages/prompts/test/__snapshots__/select.test.ts.snap @@ -49,38 +49,41 @@ exports[`select (isCI = false) > correctly limits options when message wraps to │ ● Option 0 │ ○ Option 1 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ○ Option 0 │ ● Option 1 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ... │ ● Option 2 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ● Option 3", - "", - "", + "", + "", "", "", "│ ● Option 4", - "", - "", + "", + "", "", "", "◇ This is a very @@ -669,38 +672,41 @@ exports[`select (isCI = true) > correctly limits options when message wraps to m │ ● Option 0 │ ○ Option 1 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ○ Option 0 │ ● Option 1 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ... │ ● Option 2 │ ... -│ ↑/↓ to navigate • Enter: confirm +│ ↑/↓ to navigate • Enter: +confirm └ ", - "", + "", "", "", "│ ● Option 3", - "", - "", + "", + "", "", "", "│ ● Option 4", - "", - "", + "", + "", "", "", "◇ This is a very