From 9245f330e83d27d2eef23b33637092e471430729 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 10 Dec 2025 19:09:30 -0700 Subject: [PATCH 1/2] feat: add toggle for Enter key behavior in chat input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new toggle in Settings > UI that allows users to switch between: - Enter = send, Shift+Enter = newline (default) - Enter = newline, Ctrl/Cmd+Enter = send Features: - Platform-aware labels (Ctrl on Windows/Linux, ⌘ on Mac) - Dynamic send button tooltip showing current key combination - Persisted setting via VS Code configuration API - Full i18n support for all locales Addresses user pain points around accidental submissions, ergonomic fatigue from holding Shift, and IME workflow conflicts. --- packages/types/src/global-settings.ts | 7 ++++ src/core/webview/ClineProvider.ts | 3 ++ src/shared/ExtensionMessage.ts | 1 + .../src/components/chat/ChatTextArea.tsx | 42 +++++++++++++++---- .../src/components/settings/SettingsView.tsx | 3 ++ .../src/components/settings/UISettings.tsx | 41 +++++++++++++++++- .../settings/__tests__/UISettings.spec.tsx | 1 + .../src/context/ExtensionStateContext.tsx | 5 +++ 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/de/settings.json | 4 ++ 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/fr/settings.json | 4 ++ 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/it/settings.json | 4 ++ 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 | 8 +++- webview-ui/src/i18n/locales/zh-TW/chat.json | 1 + .../src/i18n/locales/zh-TW/settings.json | 8 +++- 44 files changed, 187 insertions(+), 14 deletions(-) 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( )} - +