@@ -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,34 @@ 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+ command . register ( ( ) => [
131+ {
132+ id : PROMPT_SUBMIT_ID ,
133+ title : language . t ( "command.prompt.submit" ) ,
134+ keybind : DEFAULT_PROMPT_SUBMIT_KEYBIND ,
135+ } ,
136+ {
137+ id : PROMPT_NEWLINE_ID ,
138+ title : language . t ( "command.prompt.newline" ) ,
139+ keybind : DEFAULT_PROMPT_NEWLINE_KEYBIND ,
140+ } ,
141+ ] )
142+
143+ const submitKeybinds = createMemo ( ( ) =>
144+ parseKeybind ( settings . keybinds . get ( PROMPT_SUBMIT_ID ) ?? DEFAULT_PROMPT_SUBMIT_KEYBIND ) ,
145+ )
146+ const newlineKeybinds = createMemo ( ( ) =>
147+ parseKeybind ( settings . keybinds . get ( PROMPT_NEWLINE_ID ) ?? DEFAULT_PROMPT_NEWLINE_KEYBIND ) ,
148+ )
118149 const { params, tabs, view } = useSessionLayout ( )
119150 let editorRef ! : HTMLDivElement
120151 let fileInputRef : HTMLInputElement | undefined
@@ -1165,11 +1196,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
11651196 }
11661197 }
11671198
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 ) {
1199+ // Handle configured newline keybind BEFORE the IME check - the default
1200+ // (Shift+Enter) is never used for IME input and should always insert a
1201+ // newline regardless of composition state. Users who rebind the newline
1202+ // keybind to unmodified Enter accept that this takes precedence over IME.
1203+ if ( matchKeybind ( newlineKeybinds ( ) , event ) ) {
11711204 addPart ( { type : "text" , content : "\n" , start : 0 , end : 0 } )
11721205 event . preventDefault ( )
1206+ event . stopPropagation ( )
11731207 return
11741208 }
11751209
@@ -1232,9 +1266,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
12321266 return
12331267 }
12341268
1235- // Note: Shift+Enter is handled earlier, before IME check
1236- if ( event . key === "Enter" && ! event . shiftKey ) {
1269+ // Note: the newline keybind is handled earlier, before the IME check.
1270+ if ( matchKeybind ( submitKeybinds ( ) , event ) ) {
12371271 event . preventDefault ( )
1272+ event . stopPropagation ( )
12381273 if ( event . repeat ) return
12391274 if (
12401275 working ( ) &&
0 commit comments