@@ -28,7 +28,8 @@ import { Select } from "@opencode-ai/ui/select"
2828import { useDialog } from "@opencode-ai/ui/context/dialog"
2929import { ModelSelectorPopover } from "@/components/dialog-select-model"
3030import { useProviders } from "@/hooks/use-providers"
31- import { useCommand } from "@/context/command"
31+ import { matchKeybind , parseKeybind , useCommand } from "@/context/command"
32+ import { useSettings } from "@/context/settings"
3233import { Persist , persisted } from "@/utils/persist"
3334import { usePermission } from "@/context/permission"
3435import { useLanguage } from "@/context/language"
@@ -100,6 +101,11 @@ const EXAMPLES = [
100101
101102const NON_EMPTY_TEXT = / [ ^ \s \u200B ] /
102103
104+ const PROMPT_SUBMIT_ID = "prompt.submit"
105+ const PROMPT_NEWLINE_ID = "prompt.newline"
106+ const DEFAULT_PROMPT_SUBMIT_KEYBIND = "enter"
107+ const DEFAULT_PROMPT_NEWLINE_KEYBIND = "shift+enter"
108+
103109export const PromptInput : Component < PromptInputProps > = ( props ) => {
104110 const sdk = useSDK ( )
105111
@@ -112,9 +118,42 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
112118 const dialog = useDialog ( )
113119 const providers = useProviders ( )
114120 const command = useCommand ( )
121+ const settings = useSettings ( )
115122 const permission = usePermission ( )
116123 const language = useLanguage ( )
117124 const platform = usePlatform ( )
125+
126+ // Register Enter / Shift+Enter as rebindable commands so they surface in
127+ // the Keyboard Shortcuts settings UI (grouped under "Prompt" via the
128+ // "prompt." id prefix). The actual dispatch stays local in handleKeyDown
129+ // below so the editor always wins over the global keymap.
130+ //
131+ // `disabled: true` excludes these from the global keymap in context/command
132+ // so a focused non-editable target (e.g. a <button>) pressing Enter isn't
133+ // silently swallowed by the global handler. The catalog entries remain
134+ // visible in settings-keybinds.tsx because catalog population doesn't
135+ // filter by disabled.
136+ command . register ( ( ) => [
137+ {
138+ id : PROMPT_SUBMIT_ID ,
139+ title : language . t ( "command.prompt.submit" ) ,
140+ keybind : DEFAULT_PROMPT_SUBMIT_KEYBIND ,
141+ disabled : true ,
142+ } ,
143+ {
144+ id : PROMPT_NEWLINE_ID ,
145+ title : language . t ( "command.prompt.newline" ) ,
146+ keybind : DEFAULT_PROMPT_NEWLINE_KEYBIND ,
147+ disabled : true ,
148+ } ,
149+ ] )
150+
151+ const submitKeybinds = createMemo ( ( ) =>
152+ parseKeybind ( settings . keybinds . get ( PROMPT_SUBMIT_ID ) ?? DEFAULT_PROMPT_SUBMIT_KEYBIND ) ,
153+ )
154+ const newlineKeybinds = createMemo ( ( ) =>
155+ parseKeybind ( settings . keybinds . get ( PROMPT_NEWLINE_ID ) ?? DEFAULT_PROMPT_NEWLINE_KEYBIND ) ,
156+ )
118157 const { params, tabs, view } = useSessionLayout ( )
119158 let editorRef ! : HTMLDivElement
120159 let fileInputRef : HTMLInputElement | undefined
@@ -1165,11 +1204,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
11651204 }
11661205 }
11671206
1168- // Handle Shift+Enter BEFORE IME check - Shift+Enter is never used for IME input
1169- // and should always insert a newline regardless of composition state
1170- if ( event . key === "Enter" && event . shiftKey ) {
1207+ // Handle configured newline keybind BEFORE the IME check - the default
1208+ // (Shift+Enter) is never used for IME input and should always insert a
1209+ // newline regardless of composition state. Users who rebind the newline
1210+ // keybind to unmodified Enter accept that this takes precedence over IME.
1211+ if ( matchKeybind ( newlineKeybinds ( ) , event ) ) {
11711212 addPart ( { type : "text" , content : "\n" , start : 0 , end : 0 } )
11721213 event . preventDefault ( )
1214+ event . stopPropagation ( )
11731215 return
11741216 }
11751217
@@ -1232,9 +1274,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
12321274 return
12331275 }
12341276
1235- // Note: Shift+Enter is handled earlier, before IME check
1236- if ( event . key === "Enter" && ! event . shiftKey ) {
1277+ // Note: the newline keybind is handled earlier, before the IME check.
1278+ if ( matchKeybind ( submitKeybinds ( ) , event ) ) {
12371279 event . preventDefault ( )
1280+ event . stopPropagation ( )
12381281 if ( event . repeat ) return
12391282 if (
12401283 working ( ) &&
0 commit comments