From 71ac95443455fab56ad50a30d553c08eae6832ad Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 19:46:21 -0300 Subject: [PATCH 01/13] new: Ctrl+Enter to send message to chat draft --- packages/types/src/global-settings.ts | 1 + src/core/webview/webviewMessageHandler.ts | 8 +++++ src/shared/ExtensionMessage.ts | 1 + src/shared/WebviewMessage.ts | 1 + .../src/components/chat/ChatTextArea.tsx | 7 +++++ .../src/components/settings/SettingsView.tsx | 3 ++ .../src/components/settings/UISettings.tsx | 30 ++++++++++++++++++- .../src/context/ExtensionStateContext.tsx | 7 +++++ webview-ui/src/i18n/locales/en/settings.json | 4 +++ 9 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index a56a00fc355..1c8e878e9af 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -67,6 +67,7 @@ export const globalSettingsSchema = z.object({ alwaysAllowFollowupQuestions: z.boolean().optional(), followupAutoApproveTimeoutMs: z.number().optional(), alwaysAllowUpdateTodoList: z.boolean().optional(), + requireCtrlEnterToSend: z.boolean().optional(), allowedCommands: z.array(z.string()).optional(), deniedCommands: z.array(z.string()).optional(), commandExecutionTimeout: z.number().optional(), diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index af5f9925c35..c63d43a954b 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -577,6 +577,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("alwaysAllowUpdateTodoList", message.bool) await provider.postStateToWebview() break + case "requireCtrlEnterToSend": + await updateGlobalState("requireCtrlEnterToSend", message.bool ?? false) + await provider.postStateToWebview() + break case "askResponse": provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images) break @@ -1454,6 +1458,10 @@ export const webviewMessageHandler = async ( Terminal.setTerminalZdotdir(message.bool) } break + case "requireCtrlEnterToSend": + await updateGlobalState("requireCtrlEnterToSend", message.bool) + await provider.postStateToWebview() + break case "terminalCompressProgressBar": await updateGlobalState("terminalCompressProgressBar", message.bool) await provider.postStateToWebview() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 66f389f81c1..0b3d26806fd 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -232,6 +232,7 @@ export type ExtensionState = Pick< | "alwaysAllowFollowupQuestions" | "alwaysAllowExecute" | "alwaysAllowUpdateTodoList" + | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "allowedCommands" | "deniedCommands" diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index d43a2fce043..bc96c6fed17 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -48,6 +48,7 @@ export interface WebviewMessage { | "alwaysAllowExecute" | "alwaysAllowFollowupQuestions" | "alwaysAllowUpdateTodoList" + | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "webviewDidLaunch" | "newTask" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c7813372fa7..bb7f31333c5 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -89,6 +89,7 @@ export const ChatTextArea = forwardRef( clineMessages, commands, cloudUserInfo, + requireCtrlEnterToSend, } = useExtensionState() // Find the ID and display text for the currently selected API configuration. @@ -468,6 +469,11 @@ export const ChatTextArea = forwardRef( } if (event.key === "Enter" && !event.shiftKey && !isComposing) { + // If Ctrl+Enter is required but Ctrl key is not pressed, don't send + if (requireCtrlEnterToSend && !event.ctrlKey) { + return + } + event.preventDefault() // Always call onSend - let ChatView handle queueing when disabled @@ -536,6 +542,7 @@ export const ChatTextArea = forwardRef( handleHistoryNavigation, resetHistoryNavigation, commands, + requireCtrlEnterToSend, ], ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 93b1b39e506..041ea8ee73b 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -195,6 +195,7 @@ const SettingsView = forwardRef(({ onDone, t openRouterImageApiKey, openRouterImageGenerationSelectedModel, reasoningBlockCollapsed, + requireCtrlEnterToSend, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -384,6 +385,7 @@ const SettingsView = forwardRef(({ onDone, t vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} }) vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true }) vscode.postMessage({ type: "setReasoningBlockCollapsed", bool: reasoningBlockCollapsed ?? true }) + vscode.postMessage({ type: "requireCtrlEnterToSend", bool: requireCtrlEnterToSend ?? false }) vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting }) vscode.postMessage({ type: "profileThresholds", values: profileThresholds }) @@ -782,6 +784,7 @@ const SettingsView = forwardRef(({ onDone, t {activeTab === "ui" && ( )} diff --git a/webview-ui/src/components/settings/UISettings.tsx b/webview-ui/src/components/settings/UISettings.tsx index 2de16e68822..8a615bf0ee3 100644 --- a/webview-ui/src/components/settings/UISettings.tsx +++ b/webview-ui/src/components/settings/UISettings.tsx @@ -11,10 +11,16 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext" interface UISettingsProps extends HTMLAttributes { reasoningBlockCollapsed: boolean + requireCtrlEnterToSend?: boolean setCachedStateField: SetCachedStateField } -export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...props }: UISettingsProps) => { +export const UISettings = ({ + reasoningBlockCollapsed, + requireCtrlEnterToSend, + setCachedStateField, + ...props +}: UISettingsProps) => { const { t } = useAppTranslation() const handleReasoningBlockCollapsedChange = (value: boolean) => { @@ -26,6 +32,15 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr }) } + const handleRequireCtrlEnterToSendChange = (value: boolean) => { + setCachedStateField("requireCtrlEnterToSend", value) + + // Track telemetry event + telemetryClient.capture("ui_settings_ctrl_enter_changed", { + enabled: value, + }) + } + return (
@@ -49,6 +64,19 @@ export const UISettings = ({ reasoningBlockCollapsed, setCachedStateField, ...pr {t("settings:ui.collapseThinking.description")}
+ + {/* Require Ctrl+Enter to Send Setting */} +
+ handleRequireCtrlEnterToSendChange(e.target.checked)} + data-testid="ctrl-enter-checkbox"> + {t("settings:ui.requireCtrlEnterToSend.label")} + +
+ {t("settings:ui.requireCtrlEnterToSend.description")} +
+
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 542b2385c02..ff2161a0dca 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -158,6 +158,8 @@ export interface ExtensionStateContextType extends ExtensionState { setMaxDiagnosticMessages: (value: number) => void includeTaskHistoryInEnhance?: boolean setIncludeTaskHistoryInEnhance: (value: boolean) => void + requireCtrlEnterToSend?: boolean + setRequireCtrlEnterToSend: (value: boolean) => void } export const ExtensionStateContext = createContext(undefined) @@ -264,6 +266,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode alwaysAllowUpdateTodoList: true, includeDiagnosticMessages: true, maxDiagnosticMessages: 50, + requireCtrlEnterToSend: false, openRouterImageApiKey: "", openRouterImageGenerationSelectedModel: "", }) @@ -559,6 +562,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }, includeTaskHistoryInEnhance, setIncludeTaskHistoryInEnhance, + requireCtrlEnterToSend: state.requireCtrlEnterToSend, + setRequireCtrlEnterToSend: (value) => { + setState((prevState) => ({ ...prevState, requireCtrlEnterToSend: value })) + }, } return {children} diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index dfccc49cc4c..853b685d20b 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -42,6 +42,10 @@ "collapseThinking": { "label": "Collapse Thinking messages by default", "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" + }, + "requireCtrlEnterToSend": { + "label": "Require Ctrl+Enter to send messages", + "description": "When enabled, you must press Ctrl+Enter to send messages instead of just Enter" } }, "prompts": { From 9d206cbe7bb08efd6cc8742346429ed8f1d231b8 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 20:04:27 -0300 Subject: [PATCH 02/13] fix(Ctrl+Enter to send): UI Setting now actually saves the setting --- src/core/webview/ClineProvider.ts | 3 +++ src/core/webview/webviewMessageHandler.ts | 12 ++++-------- src/shared/ExtensionMessage.ts | 4 +++- src/shared/WebviewMessage.ts | 2 +- webview-ui/src/context/ExtensionStateContext.tsx | 6 +++++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 6a7a84b74b9..abf7ce124c9 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1793,6 +1793,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + requireCtrlEnterToSend, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -1927,6 +1928,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + requireCtrlEnterToSend: requireCtrlEnterToSend ?? false, cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, cloudOrganizations, @@ -2142,6 +2144,7 @@ export class ClineProvider maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, + requireCtrlEnterToSend: stateValues.requireCtrlEnterToSend ?? false, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c63d43a954b..179c28fbc15 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -577,10 +577,6 @@ export const webviewMessageHandler = async ( await updateGlobalState("alwaysAllowUpdateTodoList", message.bool) await provider.postStateToWebview() break - case "requireCtrlEnterToSend": - await updateGlobalState("requireCtrlEnterToSend", message.bool ?? false) - await provider.postStateToWebview() - break case "askResponse": provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images) break @@ -1458,10 +1454,6 @@ export const webviewMessageHandler = async ( Terminal.setTerminalZdotdir(message.bool) } break - case "requireCtrlEnterToSend": - await updateGlobalState("requireCtrlEnterToSend", message.bool) - await provider.postStateToWebview() - break case "terminalCompressProgressBar": await updateGlobalState("terminalCompressProgressBar", message.bool) await provider.postStateToWebview() @@ -1629,6 +1621,10 @@ export const webviewMessageHandler = async ( await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true) // No need to call postStateToWebview here as the UI already updated optimistically break + case "requireCtrlEnterToSend": + await updateGlobalState("requireCtrlEnterToSend", message.bool) + // No need to call postStateToWebview here as the UI already updated optimistically + break case "toggleApiConfigPin": if (message.text) { const currentPinned = getGlobalState("pinnedApiConfigs") ?? {} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0b3d26806fd..df5c4b799c0 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -126,8 +126,10 @@ export interface ExtensionMessage { | "insertTextIntoTextarea" | "dismissedUpsells" | "organizationSwitchResult" + | "requireCtrlEnterToSend" text?: string payload?: any // Add a generic payload for now, can refine later + bool?: boolean action?: | "chatButtonClicked" | "mcpButtonClicked" @@ -232,7 +234,6 @@ export type ExtensionState = Pick< | "alwaysAllowFollowupQuestions" | "alwaysAllowExecute" | "alwaysAllowUpdateTodoList" - | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "allowedCommands" | "deniedCommands" @@ -289,6 +290,7 @@ export type ExtensionState = Pick< | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" + | "requireCtrlEnterToSend" > & { version: string clineMessages: ClineMessage[] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index bc96c6fed17..363feed30f3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -48,7 +48,6 @@ export interface WebviewMessage { | "alwaysAllowExecute" | "alwaysAllowFollowupQuestions" | "alwaysAllowUpdateTodoList" - | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "webviewDidLaunch" | "newTask" @@ -196,6 +195,7 @@ export interface WebviewMessage { | "profileThresholds" | "setHistoryPreviewCollapsed" | "setReasoningBlockCollapsed" + | "requireCtrlEnterToSend" | "openExternal" | "filterMarketplaceItems" | "marketplaceButtonClicked" diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index ff2161a0dca..1ac7e7cc75f 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -266,7 +266,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode alwaysAllowUpdateTodoList: true, includeDiagnosticMessages: true, maxDiagnosticMessages: 50, - requireCtrlEnterToSend: false, + requireCtrlEnterToSend: false, // Default to expected value openRouterImageApiKey: "", openRouterImageGenerationSelectedModel: "", }) @@ -403,6 +403,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode } break } + case "requireCtrlEnterToSend": { + setState((prevState) => ({ ...prevState, requireCtrlEnterToSend: message.bool ?? false })) + break + } } }, [setListApiConfigMeta], From 50f03f8557b2764a6c1568551f47fed3e9900bf3 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 20:47:52 -0300 Subject: [PATCH 03/13] feat(Ctrl+Enter to send): translations for other languages --- webview-ui/src/i18n/locales/de/settings.json | 4 ++++ webview-ui/src/i18n/locales/es/settings.json | 4 ++++ webview-ui/src/i18n/locales/fr/settings.json | 4 ++++ webview-ui/src/i18n/locales/hi/settings.json | 4 ++++ webview-ui/src/i18n/locales/id/settings.json | 4 ++++ webview-ui/src/i18n/locales/it/settings.json | 4 ++++ webview-ui/src/i18n/locales/ko/settings.json | 4 ++++ webview-ui/src/i18n/locales/pl/settings.json | 4 ++++ webview-ui/src/i18n/locales/pt-BR/settings.json | 4 ++++ webview-ui/src/i18n/locales/ru/settings.json | 4 ++++ webview-ui/src/i18n/locales/tr/settings.json | 4 ++++ webview-ui/src/i18n/locales/vi/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-CN/settings.json | 4 ++++ webview-ui/src/i18n/locales/zh-TW/settings.json | 4 ++++ 14 files changed, 56 insertions(+) diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 00827751b0f..cea24370164 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -886,6 +886,10 @@ "collapseThinking": { "label": "Gedankenblöcke standardmäßig ausblenden", "description": "Wenn aktiviert, werden Gedankenblöcke standardmäßig ausgeblendet, bis du mit ihnen interagierst" + }, + "requireCtrlEnterToSend": { + "label": "Erfordert Strg+Enter zum Senden von Nachrichten", + "description": "Wenn aktiviert, musst du Strg+Enter drücken, um Nachrichten zu senden, anstatt nur Enter." } } } diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index c1271df8274..231e56f35be 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -886,6 +886,10 @@ "collapseThinking": { "label": "Colapsar mensajes de pensamiento por defecto", "description": "Cuando está activado, los bloques de pensamiento se colapsarán por defecto hasta que interactúes con ellos" + }, + "requireCtrlEnterToSend": { + "label": "Requerir Ctrl+Enter para enviar mensajes", + "description": "Cuando está habilitado, debes presionar Ctrl+Enter para enviar mensajes en lugar de solo Enter" } } } diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index abcf401d640..bd865a70eb0 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -886,6 +886,10 @@ "collapseThinking": { "label": "Réduire les messages de réflexion par défaut", "description": "Si activé, les blocs de réflexion seront réduits par défaut jusqu'à ce que vous interagissiez avec eux" + }, + "requireCtrlEnterToSend": { + "label": "Requiert Ctrl+Entrée pour envoyer des messages", + "description": "Lorsqu'activé, vous devez appuyer sur Ctrl+Entrée pour envoyer des messages au lieu de simplement Entrée" } } } diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 975e35411ee..6170a3701c7 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "सोच संदेशों को डिफ़ॉल्ट रूप से संक्षिप्त करें", "description": "सक्षम होने पर, सोच ब्लॉक आपके द्वारा उनके साथ इंटरैक्ट करने तक डिफ़ॉल्ट रूप से संक्षिप्त रहेंगे" + }, + "requireCtrlEnterToSend": { + "label": "संदेश भेजने के लिए Ctrl+Enter आवश्यक करें", + "description": "सक्षम होने पर, संदेश भेजने के लिए केवल Enter के बजाय Ctrl+Enter दबाना आवश्यक है" } } } diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index aa2c1172119..e886cadbb80 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -916,6 +916,10 @@ "collapseThinking": { "label": "Ciutkan pesan Berpikir secara default", "description": "Jika diaktifkan, blok berpikir akan diciutkan secara default sampai Anda berinteraksi dengannya" + }, + "requireCtrlEnterToSend": { + "label": "Wajib Ctrl+Enter untuk mengirim pesan", + "description": "Saat aktif, Anda harus menekan Ctrl+Enter untuk mengirim pesan, bukan hanya Enter" } } } diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 6f2e06bb8fd..c614458e08c 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Comprimi i messaggi di pensiero per impostazione predefinita", "description": "Se abilitato, i blocchi di pensiero verranno compressi per impostazione predefinita finché non interagisci con essi" + }, + "requireCtrlEnterToSend": { + "label": "Richiedi Ctrl+Invio per inviare messaggi", + "description": "Quando abilitato, devi premere Ctrl+Invio per inviare messaggi invece di solo Invio" } } } diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 61539cfc4d9..6410cfd968e 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "기본적으로 생각 메시지 접기", "description": "활성화하면 상호 작용할 때까지 생각 블록이 기본적으로 접힙니다" + }, + "requireCtrlEnterToSend": { + "label": "Ctrl+Enter를 눌러 메시지 보내기", + "description": "활성화하면 Enter 대신 Ctrl+Enter를 눌러 메시지를 보내야 합니다." } } } diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 6862d6f7edd..ba452f206a6 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Domyślnie zwijaj komunikaty o myśleniu", "description": "Gdy włączone, bloki myślenia będą domyślnie zwinięte, dopóki nie wejdziesz z nimi w interakcję" + }, + "requireCtrlEnterToSend": { + "label": "Wymaga Ctrl+Enter, aby wysłać wiadomości", + "description": "Po włączeniu musisz nacisnąć Ctrl+Enter, aby wysłać wiadomości zamiast tylko Enter." } } } diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index b8184777acf..ceffb7e905c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Recolher mensagens de pensamento por padrão", "description": "Quando ativado, os blocos de pensamento serão recolhidos por padrão até que você interaja com eles" + }, + "requireCtrlEnterToSend": { + "label": "Requer Ctrl+Enter para enviar mensagens", + "description": "Quando ativado, você deve pressionar Ctrl+Enter para enviar mensagens em vez de apenas Enter." } } } diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index bcbd72089a4..19813833841 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Сворачивать сообщения о размышлениях по умолчанию", "description": "Если включено, блоки с размышлениями будут свернуты по умолчанию, пока вы не начнете с ними взаимодействовать" + }, + "requireCtrlEnterToSend": { + "label": "Требуется Ctrl+Enter для отправки сообщений", + "description": "Если включено, необходимо нажимать Ctrl+Enter для отправки сообщений вместо одной клавиши Enter." } } } diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index 4ac28f47d2f..12fc760f3ab 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Düşünme mesajlarını varsayılan olarak daralt", "description": "Etkinleştirildiğinde, düşünme blokları siz onlarla etkileşime girene kadar varsayılan olarak daraltılır" + }, + "requireCtrlEnterToSend": { + "label": "Mesaj göndermek için Ctrl+Enter gerektirir", + "description": "Etkinleştirildiğinde, sadece Enter yerine mesaj göndermek için Ctrl+Enter tuşuna basmanız gerekir." } } } diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 4303325d068..4e2f07e97e2 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Thu gọn tin nhắn Suy nghĩ theo mặc định", "description": "Khi được bật, các khối suy nghĩ sẽ được thu gọn theo mặc định cho đến khi bạn tương tác với chúng" + }, + "requireCtrlEnterToSend": { + "label": "Yêu cầu Ctrl+Enter để gửi tin nhắn", + "description": "Khi bật, bạn phải nhấn Ctrl+Enter để gửi tin nhắn thay vì chỉ nhấn Enter." } } } diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index f574106f456..bc84375f93c 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "默认折叠“思考”消息", "description": "启用后,“思考”块将默认折叠,直到您与其交互" + }, + "requireCtrlEnterToSend": { + "label": "发送消息需按 Ctrl+Enter", + "description": "启用时,你需要按 Ctrl+Enter 才能发送消息,而不是仅按 Enter。" } } } diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index 67e8c43b60a..32504270712 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "預設折疊“思考”訊息", "description": "啟用後,“思考”塊將預設折疊,直到您與其互動" + }, + "requireCtrlEnterToSend": { + "label": "發送訊息需按 Ctrl+Enter", + "description": "啟用時,你需要按 Ctrl+Enter 才能發送訊息,而不是僅按 Enter。" } } } From 482107156c4e1a46e45513e52fbae7557a5c06c6 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:00:09 -0300 Subject: [PATCH 04/13] feat(Ctrl+Enter to send): compatibility with macos --- webview-ui/src/components/chat/ChatTextArea.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index bb7f31333c5..14c8e8b7e56 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -469,8 +469,8 @@ export const ChatTextArea = forwardRef( } if (event.key === "Enter" && !event.shiftKey && !isComposing) { - // If Ctrl+Enter is required but Ctrl key is not pressed, don't send - if (requireCtrlEnterToSend && !event.ctrlKey) { + // If Ctrl+Enter is required but neither Ctrl nor Meta (Cmd) key is pressed, don't send + if (requireCtrlEnterToSend && !event.ctrlKey && !event.metaKey) { return } From a370dd5d348681cacfdb49e513d11ca9c3bd7954 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:00:47 -0300 Subject: [PATCH 05/13] feat(Ctrl+Enter to send): added tests for setting --- .../chat/__tests__/ChatTextArea.spec.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx index af7704aa1b7..8732115221f 100644 --- a/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatTextArea.spec.tsx @@ -1058,6 +1058,85 @@ describe("ChatTextArea", () => { }) }) + describe("keyboard handling with requireCtrlEnterToSend", () => { + beforeEach(() => { + ;(useExtensionState as ReturnType).mockReturnValue({ + filePaths: [], + openedTabs: [], + apiConfiguration: { + apiProvider: "anthropic", + }, + taskHistory: [], + cwd: "/test/workspace", + requireCtrlEnterToSend: true, + }) + }) + + it("should send message with Ctrl+Enter when requireCtrlEnterToSend is enabled", () => { + const onSend = vi.fn() + const { container } = render() + + const textarea = container.querySelector("textarea")! + fireEvent.keyDown(textarea, { key: "Enter", ctrlKey: true }) + + expect(onSend).toHaveBeenCalled() + }) + + it("should send message with Cmd+Enter when requireCtrlEnterToSend is enabled", () => { + const onSend = vi.fn() + const { container } = render() + + const textarea = container.querySelector("textarea")! + fireEvent.keyDown(textarea, { key: "Enter", metaKey: true }) + + expect(onSend).toHaveBeenCalled() + }) + + it("should not send message with regular Enter when requireCtrlEnterToSend is enabled", () => { + const onSend = vi.fn() + const { container } = render() + + const textarea = container.querySelector("textarea")! + fireEvent.keyDown(textarea, { key: "Enter" }) + + expect(onSend).not.toHaveBeenCalled() + }) + + it("should insert newline with Shift+Enter when requireCtrlEnterToSend is enabled", () => { + const setInputValue = vi.fn() + const { container } = render( + , + ) + + const textarea = container.querySelector("textarea")! + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: true }) + + // Should not call onSend, allowing default behavior (insert newline) + expect(setInputValue).not.toHaveBeenCalled() + }) + + it("should send message with regular Enter when requireCtrlEnterToSend is disabled", () => { + ;(useExtensionState as ReturnType).mockReturnValue({ + filePaths: [], + openedTabs: [], + apiConfiguration: { + apiProvider: "anthropic", + }, + taskHistory: [], + cwd: "/test/workspace", + requireCtrlEnterToSend: false, + }) + + const onSend = vi.fn() + const { container } = render() + + const textarea = container.querySelector("textarea")! + fireEvent.keyDown(textarea, { key: "Enter" }) + + expect(onSend).toHaveBeenCalled() + }) + }) + describe("send button visibility", () => { it("should show send button when there are images but no text", () => { const { container } = render( From 33112b23fc68ced80e01679c0f04a8d2275fcb1f Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:10:01 -0300 Subject: [PATCH 06/13] feat(Ctrl+Enter to send): added missing translations --- webview-ui/src/i18n/locales/ca/settings.json | 4 ++++ webview-ui/src/i18n/locales/ja/settings.json | 4 ++++ webview-ui/src/i18n/locales/nl/settings.json | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 611159069b2..dc626d8fc5c 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -886,6 +886,10 @@ "collapseThinking": { "label": "Replega els missatges de pensament per defecte", "description": "Quan estigui activat, els blocs de pensament es replegaran per defecte fins que interactuïs amb ells" + }, + "requireCtrlEnterToSend": { + "label": "Requereix Ctrl+Enter per enviar missatges", + "description": "Quan estigui activat, hauràs de prémer Ctrl+Enter per enviar missatges en lloc de només Enter" } } } diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index cc1ea09317e..3dd0ae7f91c 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "デフォルトで思考メッセージを折りたたむ", "description": "有効にすると、操作するまで思考ブロックがデフォルトで折りたたまれます" + }, + "requireCtrlEnterToSend": { + "label": "メッセージ送信にCtrl+Enterを必須にする", + "description": "有効にすると、EnterキーだけでなくCtrl+Enterを押す必要があります" } } } diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 41ee3e5910b..da9790f029c 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -887,6 +887,10 @@ "collapseThinking": { "label": "Denkberichten standaard samenvouwen", "description": "Indien ingeschakeld, worden denkblokken standaard samengevouwen totdat je ermee interageert" + }, + "requireCtrlEnterToSend": { + "label": "Vereis Ctrl+Enter om berichten te verzenden", + "description": "Wanneer ingeschakeld, moet u Ctrl+Enter indrukken om berichten te verzenden in plaats van alleen Enter" } } } From abd20bc0a5bbd994697da5fb4b3f5e70f0755e13 Mon Sep 17 00:00:00 2001 From: Lorenzo <57605930+lmtr0@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:05:51 -0300 Subject: [PATCH 07/13] feat: add Cmd to label Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> --- webview-ui/src/i18n/locales/en/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 853b685d20b..91a5a5eb713 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -44,7 +44,8 @@ "description": "When enabled, thinking blocks will be collapsed by default until you interact with them" }, "requireCtrlEnterToSend": { - "label": "Require Ctrl+Enter to send messages", + "label": "Require Ctrl/Cmd+Enter to send messages", + "description": "When enabled, you must press Ctrl or Cmd+Enter to send messages instead of just Enter" "description": "When enabled, you must press Ctrl+Enter to send messages instead of just Enter" } }, From 942264401207891684c2739a3c6dfb72abd0a2b5 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 22:07:19 -0300 Subject: [PATCH 08/13] fix(Ctrl+Enter to send): removing incorrect change from roomote --- webview-ui/src/i18n/locales/en/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 91a5a5eb713..deeb7944482 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -46,7 +46,6 @@ "requireCtrlEnterToSend": { "label": "Require Ctrl/Cmd+Enter to send messages", "description": "When enabled, you must press Ctrl or Cmd+Enter to send messages instead of just Enter" - "description": "When enabled, you must press Ctrl+Enter to send messages instead of just Enter" } }, "prompts": { From 0897c677573cf8ec32fc5a10a285f47571f3ad62 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Sat, 1 Nov 2025 18:17:03 -0300 Subject: [PATCH 09/13] refactor: addressing pr comments made in #8556 --- .../src/components/chat/ChatTextArea.tsx | 25 +++++++++- .../src/components/settings/UISettings.tsx | 9 +++- webview-ui/src/i18n/locales/ca/chat.json | 1 + webview-ui/src/i18n/locales/ca/settings.json | 4 +- webview-ui/src/i18n/locales/de/chat.json | 1 + webview-ui/src/i18n/locales/en/chat.json | 1 + webview-ui/src/i18n/locales/en/settings.json | 4 +- webview-ui/src/i18n/locales/es/chat.json | 1 + webview-ui/src/i18n/locales/es/settings.json | 4 +- webview-ui/src/i18n/locales/fr/chat.json | 1 + webview-ui/src/i18n/locales/hi/chat.json | 1 + webview-ui/src/i18n/locales/hi/settings.json | 4 +- webview-ui/src/i18n/locales/id/chat.json | 1 + webview-ui/src/i18n/locales/id/settings.json | 4 +- webview-ui/src/i18n/locales/it/chat.json | 1 + webview-ui/src/i18n/locales/ja/chat.json | 1 + webview-ui/src/i18n/locales/ja/settings.json | 4 +- webview-ui/src/i18n/locales/ko/chat.json | 1 + webview-ui/src/i18n/locales/ko/settings.json | 4 +- webview-ui/src/i18n/locales/nl/chat.json | 1 + webview-ui/src/i18n/locales/nl/settings.json | 4 +- webview-ui/src/i18n/locales/pl/chat.json | 1 + webview-ui/src/i18n/locales/pl/settings.json | 4 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 1 + .../src/i18n/locales/pt-BR/settings.json | 4 +- webview-ui/src/i18n/locales/ru/chat.json | 1 + webview-ui/src/i18n/locales/ru/settings.json | 4 +- webview-ui/src/i18n/locales/tr/chat.json | 1 + webview-ui/src/i18n/locales/tr/settings.json | 4 +- webview-ui/src/i18n/locales/vi/chat.json | 1 + webview-ui/src/i18n/locales/vi/settings.json | 4 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 1 + .../src/i18n/locales/zh-CN/settings.json | 4 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 1 + .../src/i18n/locales/zh-TW/settings.json | 4 +- webview-ui/src/utils/platform.ts | 47 +++++++++++++++++++ 36 files changed, 126 insertions(+), 33 deletions(-) create mode 100644 webview-ui/src/utils/platform.ts diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 14c8e8b7e56..6aa1983db57 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -11,6 +11,7 @@ import { ExtensionMessage } from "@roo/ExtensionMessage" import { vscode } from "@src/utils/vscode" import { useExtensionState } from "@src/context/ExtensionStateContext" import { useAppTranslation } from "@src/i18n/TranslationContext" +import { getSendMessageKeyCombination } from "@src/utils/platform" import { ContextMenuOptionType, getContextMenuOptions, @@ -1161,7 +1162,13 @@ export const ChatTextArea = forwardRef( )} - +