diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 1e6e621b697..9b93f0076de 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -187,6 +187,13 @@ export const globalSettingsSchema = z.object({ includeTaskHistoryInEnhance: z.boolean().optional(), historyPreviewCollapsed: z.boolean().optional(), reasoningBlockCollapsed: z.boolean().optional(), + /** + * Controls the keyboard behavior for sending messages in the chat input. + * - "send": Enter sends message, Shift+Enter creates newline (default) + * - "newline": Enter creates newline, Shift+Enter/Ctrl+Enter sends message + * @default "send" + */ + enterBehavior: z.enum(["send", "newline"]).optional(), profileThresholds: z.record(z.string(), z.number()).optional(), hasOpenedModeSelector: z.boolean().optional(), lastModeExportPath: z.string().optional(), diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8cc9bdd48df..0beb218969c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1872,6 +1872,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, reasoningBlockCollapsed, + enterBehavior, cloudUserInfo, cloudIsAuthenticated, sharingEnabled, @@ -2025,6 +2026,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, reasoningBlockCollapsed: reasoningBlockCollapsed ?? true, + enterBehavior: enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated: cloudIsAuthenticated ?? false, cloudOrganizations, @@ -2254,6 +2256,7 @@ export class ClineProvider maxConcurrentFileReads: stateValues.maxConcurrentFileReads ?? 5, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true, + enterBehavior: stateValues.enterBehavior ?? "send", cloudUserInfo, cloudIsAuthenticated, sharingEnabled, diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 2b478c5ba02..70e07dea796 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -286,6 +286,7 @@ export type ExtensionState = Pick< | "openRouterImageGenerationSelectedModel" | "includeTaskHistoryInEnhance" | "reasoningBlockCollapsed" + | "enterBehavior" | "includeCurrentTime" | "includeCurrentCost" | "maxGitStatusFiles" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 58f42a367bc..7fbc6587181 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, + enterBehavior, } = useExtensionState() // Find the ID and display text for the currently selected API configuration. @@ -257,6 +258,17 @@ export const ChatTextArea = forwardRef( return inputValue.trim().length > 0 || selectedImages.length > 0 }, [inputValue, selectedImages]) + // Compute the key combination text for the send button tooltip based on enterBehavior + const sendKeyCombination = useMemo(() => { + if (enterBehavior === "newline") { + // When Enter = newline, Ctrl/Cmd+Enter sends + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0 + return isMac ? "⌘+Enter" : "Ctrl+Enter" + } + // Default: Enter sends + return "Enter" + }, [enterBehavior]) + const queryItems = useMemo(() => { return [ { type: ContextMenuOptionType.Problems, value: "problems" }, @@ -472,12 +484,24 @@ export const ChatTextArea = forwardRef( return } - if (event.key === "Enter" && !event.shiftKey && !isComposing) { - event.preventDefault() - - // Always call onSend - let ChatView handle queueing when disabled - resetHistoryNavigation() - onSend() + // Handle Enter key based on enterBehavior setting + if (event.key === "Enter" && !isComposing) { + if (enterBehavior === "newline") { + // New behavior: Enter = newline, Shift+Enter or Ctrl+Enter = send + if (event.shiftKey || event.ctrlKey || event.metaKey) { + event.preventDefault() + resetHistoryNavigation() + onSend() + } + // Otherwise, let Enter create newline (don't preventDefault) + } else { + // Default behavior: Enter = send, Shift+Enter = newline + if (!event.shiftKey) { + event.preventDefault() + resetHistoryNavigation() + onSend() + } + } } if (event.key === "Backspace" && !isComposing) { @@ -541,6 +565,7 @@ export const ChatTextArea = forwardRef( handleHistoryNavigation, resetHistoryNavigation, commands, + enterBehavior, ], ) @@ -1159,9 +1184,10 @@ export const ChatTextArea = forwardRef( )} - +