Skip to content

Commit 48752ac

Browse files
fix(app): make prompt submit and newline rebindable
Register `prompt.submit` (default `enter`) and `prompt.newline` (default `shift+enter`) as web commands so they appear in the existing Settings → Keyboard Shortcuts UI alongside every other rebindable shortcut. `handleKeyDown` in prompt-input.tsx now dispatches via `matchKeybind` against the configured keybinds instead of hardcoded `event.key === "Enter"` checks. Defaults preserve current behavior byte-for-byte. Related: #16226, #11898, #9836
1 parent 33b2795 commit 48752ac

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

packages/app/src/components/prompt-input.tsx

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import { Select } from "@opencode-ai/ui/select"
2828
import { useDialog } from "@opencode-ai/ui/context/dialog"
2929
import { ModelSelectorPopover } from "@/components/dialog-select-model"
3030
import { 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"
3233
import { Persist, persisted } from "@/utils/persist"
3334
import { usePermission } from "@/context/permission"
3435
import { useLanguage } from "@/context/language"
@@ -100,6 +101,11 @@ const EXAMPLES = [
100101

101102
const 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+
103109
export 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() &&

packages/app/src/i18n/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export const dict = {
7373
"command.model.variant.cycle.description": "Switch to the next effort level",
7474
"command.prompt.mode.shell": "Shell",
7575
"command.prompt.mode.normal": "Prompt",
76+
"command.prompt.submit": "Submit prompt",
77+
"command.prompt.newline": "Insert newline in prompt",
7678
"command.permissions.autoaccept.enable": "Auto-accept permissions",
7779
"command.permissions.autoaccept.disable": "Stop auto-accepting permissions",
7880
"command.workspace.toggle": "Toggle workspaces",

0 commit comments

Comments
 (0)