diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 579356ae2d45..464ee482014f 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -82,6 +82,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/ClineProvider.ts b/src/core/webview/ClineProvider.ts index f73a36d445ee..7a7f3030d63b 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1858,6 +1858,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + requireCtrlEnterToSend, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -1995,6 +1996,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + requireCtrlEnterToSend: requireCtrlEnterToSend ?? false, cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, cloudOrganizations, @@ -2213,6 +2215,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 c06729674503..9cc44c5f9b76 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1684,6 +1684,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 7d2759c91905..5082bd8a2e0f 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -127,6 +127,7 @@ export interface ExtensionMessage { | "insertTextIntoTextarea" | "dismissedUpsells" | "organizationSwitchResult" + | "requireCtrlEnterToSend" text?: string payload?: any // Add a generic payload for now, can refine later // Checkpoint warning message @@ -134,6 +135,7 @@ export interface ExtensionMessage { type: "WAIT_TIMEOUT" | "INIT_TIMEOUT" timeout: number } + bool?: boolean action?: | "chatButtonClicked" | "mcpButtonClicked" @@ -296,6 +298,7 @@ export type ExtensionState = Pick< | "reasoningBlockCollapsed" | "includeCurrentTime" | "includeCurrentCost" + | "requireCtrlEnterToSend" > & { version: string clineMessages: ClineMessage[] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index f10808cd428d..ecb0b449fdd5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -199,6 +199,7 @@ export interface WebviewMessage { | "profileThresholds" | "setHistoryPreviewCollapsed" | "setReasoningBlockCollapsed" + | "requireCtrlEnterToSend" | "openExternal" | "filterMarketplaceItems" | "marketplaceButtonClicked" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c7813372fa79..e7a5d6f5adde 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, @@ -89,6 +90,7 @@ export const ChatTextArea = forwardRef( clineMessages, commands, cloudUserInfo, + requireCtrlEnterToSend, } = useExtensionState() // Find the ID and display text for the currently selected API configuration. @@ -468,6 +470,11 @@ export const ChatTextArea = forwardRef( } if (event.key === "Enter" && !event.shiftKey && !isComposing) { + // If Ctrl+Enter is required but neither Ctrl nor Meta (Cmd) key is pressed, don't send + if (requireCtrlEnterToSend && !event.ctrlKey && !event.metaKey) { + return + } + event.preventDefault() // Always call onSend - let ChatView handle queueing when disabled @@ -536,6 +543,7 @@ export const ChatTextArea = forwardRef( handleHistoryNavigation, resetHistoryNavigation, commands, + requireCtrlEnterToSend, ], ) @@ -995,11 +1003,8 @@ export const ChatTextArea = forwardRef( "font-vscode-font-family", "text-vscode-editor-font-size", "leading-vscode-editor-line-height", - isFocused - ? "border border-vscode-focusBorder outline outline-vscode-focusBorder" - : isDraggingOver - ? "border-2 border-dashed border-vscode-focusBorder" - : "border border-transparent", + "border-none", + "outline-none", "pl-2", "py-2", isEditMode ? "pr-20" : "pr-9", @@ -1154,7 +1159,13 @@ export const ChatTextArea = forwardRef( )} - +