From a77c4c7ebec2cb8dbbfd66cb4d9d5551253cdb31 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:39:54 +0800 Subject: [PATCH] feat: Enter in the inline prompt textarea submits; Shift/Ctrl+Enter newline Chat-input convention for the inline prompt's textarea: - Enter (no modifier): preventDefault() + click the send button (which validates non-empty text and calls onSubmit with the current text + the selected provider/model). - Shift+Enter / Ctrl+Enter: do NOT preventDefault, so the textarea inserts a newline (the default browser behavior). Implementation: - opencode_inline_prompt.ts: add a keydown listener on the textarea in the constructor (right after the send button setup, so this._sendBtn is already assigned and the closure can call this._sendBtn.click()). Tests: - 'Enter in the textarea submits; Shift+Enter does not': dispatches keydown Enter on the textarea and asserts the onSubmit jest.fn() is called once with the textarea value and the current provider/model. Then dispatches Shift+Enter and Ctrl+Enter and asserts onSubmit is NOT called. Verification: pytest 37/37, jest 36/36 (was 35, +1). Co-Authored-By: Claude Fable 5 --- src/__tests__/opencode_cell_actions.spec.ts | 55 +++++++++++++++++++++ src/components/opencode_inline_prompt.ts | 8 +++ 2 files changed, 63 insertions(+) diff --git a/src/__tests__/opencode_cell_actions.spec.ts b/src/__tests__/opencode_cell_actions.spec.ts index b16d36e..9ac2e0c 100644 --- a/src/__tests__/opencode_cell_actions.spec.ts +++ b/src/__tests__/opencode_cell_actions.spec.ts @@ -619,4 +619,59 @@ describe('OpenCodeInlinePrompt', () => { const writeText = (navigator.clipboard.writeText as unknown as jest.Mock); expect(writeText).toHaveBeenCalledWith('print(1)'); }); + + it('Enter in the textarea submits; Shift+Enter does not', () => { + const cell = makeFakeCell('x = 1'); + const onSubmit = jest.fn(); + const prompt = new OpenCodeInlinePrompt(cell, { + onSubmit, + onCancel: jest.fn(), + disabled: false, + providers: null + }); + prompt.setMessages([ + { role: 'user', content: '' }, + { role: 'assistant', content: 'noop' } + ]); + const textarea = prompt.node.querySelector( + 'textarea' + ) as HTMLTextAreaElement; + textarea.value = 'fix the bug'; + + // Plain Enter submits with the current text + the current + // provider/model selection (both default to "" / first when null). + textarea.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Enter', + bubbles: true, + cancelable: true + }) + ); + expect(onSubmit).toHaveBeenCalledTimes(1); + expect(onSubmit).toHaveBeenCalledWith('fix the bug', undefined, undefined); + + // Shift+Enter must NOT submit (chat convention: newline). + onSubmit.mockClear(); + textarea.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Enter', + shiftKey: true, + bubbles: true, + cancelable: true + }) + ); + expect(onSubmit).not.toHaveBeenCalled(); + + // Ctrl+Enter also must not submit (newline). + onSubmit.mockClear(); + textarea.dispatchEvent( + new KeyboardEvent('keydown', { + key: 'Enter', + ctrlKey: true, + bubbles: true, + cancelable: true + }) + ); + expect(onSubmit).not.toHaveBeenCalled(); + }); }); diff --git a/src/components/opencode_inline_prompt.ts b/src/components/opencode_inline_prompt.ts index a4fdc6c..f7328e0 100644 --- a/src/components/opencode_inline_prompt.ts +++ b/src/components/opencode_inline_prompt.ts @@ -125,6 +125,14 @@ export class OpenCodeInlinePrompt extends Widget { const modelId = this._modelSelect?.value || undefined; options.onSubmit(text, providerId, modelId); }); + // Chat-input convention: Enter submits, Shift+Enter / Ctrl+Enter + // insert a newline (textarea default). + this._textarea.addEventListener('keydown', (e) => { + if (e.key === 'Enter' && !e.shiftKey && !e.ctrlKey) { + e.preventDefault(); + this._sendBtn.click(); + } + }); this._cancelBtn = document.createElement('button'); this._cancelBtn.className = 'opencode-btn-cancel'; this._cancelBtn.textContent = '取消';