diff --git a/.changeset/ten-vans-stand.md b/.changeset/ten-vans-stand.md new file mode 100644 index 00000000..dd2df540 --- /dev/null +++ b/.changeset/ten-vans-stand.md @@ -0,0 +1,10 @@ +--- +"@clack/core": patch +--- + +fix: only submit multi-line prompt when double-return happens at end of input. + +Also fixes two minor things: + +- Initial value is used as initial user input for multi-line prompts +- Cursor is placed at the end when there is initial input diff --git a/packages/core/src/prompts/multi-line.ts b/packages/core/src/prompts/multi-line.ts index dcec90af..e8c881f7 100644 --- a/packages/core/src/prompts/multi-line.ts +++ b/packages/core/src/prompts/multi-line.ts @@ -72,7 +72,7 @@ export default class MultiLinePrompt extends Prompt { } const wasReturn = this.#lastKeyWasReturn; this.#lastKeyWasReturn = true; - if (wasReturn) { + if (wasReturn && this.cursor === this.userInput.length) { if (this.userInput[this.cursor - 1] === '\n') { this._setUserInput( this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor) @@ -87,11 +87,25 @@ export default class MultiLinePrompt extends Prompt { } constructor(opts: MultiLineOptions) { - super(opts, false); + const initialUserInput = opts.initialUserInput ?? opts.initialValue; + + super( + { + ...opts, + initialUserInput, + }, + false + ); + + if (initialUserInput !== undefined) { + this._cursor = initialUserInput.length; + } + this.#showSubmit = opts.showSubmit ?? false; this.on('key', (char, key) => { if (key?.name && cursorActions.has(key.name as CursorAction)) { + this.#lastKeyWasReturn = false; this.#handleCursor(key.name as CursorAction); return; } diff --git a/packages/core/test/prompts/multi-line.test.ts b/packages/core/test/prompts/multi-line.test.ts index 2b92f804..c04e6183 100644 --- a/packages/core/test/prompts/multi-line.test.ts +++ b/packages/core/test/prompts/multi-line.test.ts @@ -57,6 +57,34 @@ describe('MultiLinePrompt', () => { expect(result).to.equal('x'); }); + test('sets initial value from initialValue', async () => { + const instance = new MultiLinePrompt({ + input, + output, + render: () => 'foo', + initialValue: 'bleep bloop', + }); + const resultPromise = instance.prompt(); + input.emit('keypress', '', { name: 'return' }); + input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('bleep bloop'); + }); + + test('sets initial value from initialUserInput', async () => { + const instance = new MultiLinePrompt({ + input, + output, + render: () => 'foo', + initialUserInput: 'bleep bloop', + }); + const resultPromise = instance.prompt(); + input.emit('keypress', '', { name: 'return' }); + input.emit('keypress', '', { name: 'return' }); + const result = await resultPromise; + expect(result).to.equal('bleep bloop'); + }); + describe('cursor', () => { test('can get cursor', () => { const instance = new MultiLinePrompt({ @@ -70,17 +98,36 @@ describe('MultiLinePrompt', () => { }); describe('userInputWithCursor', () => { - test('returns value on submit', () => { + test('returns value on submit', async () => { const instance = new MultiLinePrompt({ input, output, render: () => 'foo', }); - instance.prompt(); + const resultPromise = instance.prompt(); input.emit('keypress', 'x', { name: 'x' }); input.emit('keypress', '', { name: 'return' }); input.emit('keypress', '', { name: 'return' }); expect(instance.userInputWithCursor).to.equal('x'); + const value = await resultPromise; + expect(value).to.equal('x'); + }); + + test('double return does not submit mid-value', async () => { + const instance = new MultiLinePrompt({ + input, + output, + render: () => 'foo', + }); + const resultPromise = instance.prompt(); + input.emit('keypress', 'x', { name: 'x' }); + input.emit('keypress', 'y', { name: 'y' }); + input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'return' }); + input.emit('keypress', '', { name: 'return' }); + expect(instance.userInput).to.equal('x\n\ny'); + input.emit('keypress', '', { name: 'escape' }); + await resultPromise; }); test('highlights cursor position', () => { @@ -257,10 +304,9 @@ describe('MultiLinePrompt', () => { input.emit('keypress', 'x', { name: 'x' }); input.emit('keypress', '', { name: 'left' }); input.emit('keypress', '', { name: 'backspace' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('x'); + expect(instance.userInput).to.equal('x'); + input.emit('keypress', '', { name: 'escape' }); + await resultPromise; }); test('left moves left until start', async () => { @@ -274,10 +320,9 @@ describe('MultiLinePrompt', () => { input.emit('keypress', '', { name: 'left' }); input.emit('keypress', '', { name: 'left' }); input.emit('keypress', 'y', { name: 'y' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('yx'); + expect(instance.userInput).to.equal('yx'); + input.emit('keypress', '', { name: 'escape' }); + await resultPromise; }); test('right moves right until end', async () => { @@ -312,10 +357,9 @@ describe('MultiLinePrompt', () => { input.emit('keypress', '', { name: 'left' }); input.emit('keypress', '', { name: 'left' }); input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xz\ny'); + expect(instance.userInput).to.equal('xz\ny'); + input.emit('keypress', '', { name: 'escape' }); + await resultPromise; }); test('right moves across lines', async () => { @@ -351,10 +395,9 @@ describe('MultiLinePrompt', () => { input.emit('keypress', 'y', { name: 'y' }); input.emit('keypress', '', { name: 'up' }); input.emit('keypress', 'z', { name: 'z' }); - input.emit('keypress', '', { name: 'return' }); - input.emit('keypress', '', { name: 'return' }); - const result = await resultPromise; - expect(result).to.equal('xz\ny'); + expect(instance.userInput).to.equal('xz\ny'); + input.emit('keypress', '', { name: 'escape' }); + await resultPromise; }); test('down moves down a line', async () => {