From b053f32a8dc28625d999142bb9189f8411d26003 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 19:46:21 -0300 Subject: [PATCH 01/18] new: Ctrl+Enter to send message to chat draft --- packages/types/src/global-settings.ts | 1 + src/shared/ExtensionMessage.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 +++ 7 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 6e61c3950f5..257bcb2645d 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -83,6 +83,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/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 0d50f0ed487..2535a788324 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -239,6 +239,7 @@ export type ExtensionState = Pick< | "alwaysAllowFollowupQuestions" | "alwaysAllowExecute" | "alwaysAllowUpdateTodoList" + | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "allowedCommands" | "deniedCommands" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 58f42a367bc..860a9c2bb2f 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -94,6 +94,7 @@ export const ChatTextArea = forwardRef( clineMessages, commands, cloudUserInfo, + requireCtrlEnterToSend, } = useExtensionState() // Find the ID and display text for the currently selected API configuration. @@ -473,6 +474,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 @@ -541,6 +547,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 4e0dc638b7f..93fb028387d 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -213,6 +213,7 @@ const SettingsView = forwardRef(({ onDone, t includeCurrentTime, includeCurrentCost, maxGitStatusFiles, + requireCtrlEnterToSend, } = cachedState const apiConfiguration = useMemo(() => cachedState.apiConfiguration ?? {}, [cachedState.apiConfiguration]) @@ -416,6 +417,7 @@ const SettingsView = forwardRef(({ onDone, t includeCurrentTime: includeCurrentTime ?? true, includeCurrentCost: includeCurrentCost ?? true, maxGitStatusFiles: maxGitStatusFiles ?? 0, + requireCtrlEnterToSend: requireCtrlEnterToSend ?? false, profileThresholds, imageGenerationProvider, openRouterImageApiKey, @@ -833,6 +835,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 4bc03e259c7..c96d0541e98 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -165,6 +165,8 @@ export interface ExtensionStateContextType extends ExtensionState { setIncludeCurrentTime: (value: boolean) => void includeCurrentCost?: boolean setIncludeCurrentCost: (value: boolean) => void + requireCtrlEnterToSend?: boolean + setRequireCtrlEnterToSend: (value: boolean) => void } export const ExtensionStateContext = createContext(undefined) @@ -273,6 +275,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode alwaysAllowUpdateTodoList: true, includeDiagnosticMessages: true, maxDiagnosticMessages: 50, + requireCtrlEnterToSend: false, openRouterImageApiKey: "", openRouterImageGenerationSelectedModel: "", includeCurrentTime: true, @@ -597,6 +600,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setIncludeCurrentTime, includeCurrentCost, setIncludeCurrentCost, + 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 6dd07ca4110..2577e4af8d6 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -64,6 +64,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 5356597bdf7bcaf79f684cbe5f7b10d8a7b917fa Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 20:04:27 -0300 Subject: [PATCH 02/18] fix(Ctrl+Enter to send): UI Setting now actually saves the setting --- src/core/webview/ClineProvider.ts | 3 +++ src/shared/ExtensionMessage.ts | 4 +++- webview-ui/src/context/ExtensionStateContext.tsx | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 3e054ce7d25..377f10bf64f 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1905,6 +1905,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + requireCtrlEnterToSend, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -2058,6 +2059,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + requireCtrlEnterToSend: requireCtrlEnterToSend ?? false, cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, cloudOrganizations, @@ -2287,6 +2289,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/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 2535a788324..e892c72075b 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -131,6 +131,7 @@ export interface ExtensionMessage { | "interactionRequired" | "browserSessionUpdate" | "browserSessionNavigate" + | "requireCtrlEnterToSend" text?: string payload?: any // Add a generic payload for now, can refine later // Checkpoint warning message @@ -138,6 +139,7 @@ export interface ExtensionMessage { type: "WAIT_TIMEOUT" | "INIT_TIMEOUT" timeout: number } + bool?: boolean action?: | "chatButtonClicked" | "settingsButtonClicked" @@ -239,7 +241,6 @@ export type ExtensionState = Pick< | "alwaysAllowFollowupQuestions" | "alwaysAllowExecute" | "alwaysAllowUpdateTodoList" - | "requireCtrlEnterToSend" | "followupAutoApproveTimeoutMs" | "allowedCommands" | "deniedCommands" @@ -289,6 +290,7 @@ export type ExtensionState = Pick< | "includeCurrentTime" | "includeCurrentCost" | "maxGitStatusFiles" + | "requireCtrlEnterToSend" > & { version: string clineMessages: ClineMessage[] diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index c96d0541e98..af80d4b174f 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -275,7 +275,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: "", includeCurrentTime: true, @@ -425,6 +425,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode } break } + case "requireCtrlEnterToSend": { + setState((prevState) => ({ ...prevState, requireCtrlEnterToSend: message.bool ?? false })) + break + } } }, [setListApiConfigMeta], From ce2327985aef9a83d856785446b4de542e3c31aa Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 20:47:52 -0300 Subject: [PATCH 03/18] 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 53799baca6a..7b2d59b2db6 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -955,6 +955,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 25a7ee2128f..3d6e2afbd30 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -955,6 +955,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 f53715ec1e4..0836b4ce47e 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -955,6 +955,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 3d6ab6969c4..0e1a2dd247c 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -956,6 +956,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 c519f0dccb9..5c128818e18 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -985,6 +985,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 568cf19d47c..645bb019a64 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -956,6 +956,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 fcbda4acebe..22f50ffe1f8 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -956,6 +956,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 08225847b92..e03207d0bf4 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -956,6 +956,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 a1f4155a879..1e352097d5c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -956,6 +956,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 e71ddfd936b..6525ab39d62 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -956,6 +956,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 0e17e1a6cc9..dd9116ad7d5 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -956,6 +956,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 f164db9ac90..f4a56f14e8b 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -956,6 +956,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 2c9327c94db..37185da3bb6 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -956,6 +956,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 67957e87afb..7a7a0d18954 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -956,6 +956,10 @@ "collapseThinking": { "label": "預設折疊“思考”訊息", "description": "啟用後,“思考”塊將預設折疊,直到您與其互動" + }, + "requireCtrlEnterToSend": { + "label": "發送訊息需按 Ctrl+Enter", + "description": "啟用時,你需要按 Ctrl+Enter 才能發送訊息,而不是僅按 Enter。" } } } From 2689f8a54c52655de4da4f48b7bae6ba837956a2 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:00:09 -0300 Subject: [PATCH 04/18] 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 860a9c2bb2f..e22f7fa744a 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -474,8 +474,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 329cc4f9c8c5b19b3396672c6beabbbd5fc33283 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:00:47 -0300 Subject: [PATCH 05/18] 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 3a96793afc42bd75883129fbd1135b1fbed90b50 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 21:10:01 -0300 Subject: [PATCH 06/18] 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 1d2124b6fba..b922061e369 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -955,6 +955,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 00b45386301..d16f3c0f868 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -956,6 +956,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 404e35e0da1..3f7a718eb4b 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -956,6 +956,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 c4865422dc46e9dc0f9ec48181a521af2df1ef33 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/18] 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 2577e4af8d6..d3b551beb55 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -66,7 +66,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 e5669a251ecb47cf80050761ef16c7df65d53614 Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Tue, 7 Oct 2025 22:07:19 -0300 Subject: [PATCH 08/18] 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 d3b551beb55..7142c3e4261 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -68,7 +68,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 096d4ad6c5f4dabd163e4054aa381fb77034f3ce Mon Sep 17 00:00:00 2001 From: lmtr0 Date: Sat, 1 Nov 2025 18:17:03 -0300 Subject: [PATCH 09/18] 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 e22f7fa744a..abca6d3c32a 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, @@ -1166,7 +1167,13 @@ export const ChatTextArea = forwardRef( )} - +