Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import { Select } from "@opencode-ai/ui/select"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { ModelSelectorPopover } from "@/components/dialog-select-model"
import { useProviders } from "@/hooks/use-providers"
import { useCommand } from "@/context/command"
import { matchKeybind, parseKeybind, useCommand } from "@/context/command"
import { useSettings } from "@/context/settings"
import { Persist, persisted } from "@/utils/persist"
import { usePermission } from "@/context/permission"
import { useLanguage } from "@/context/language"
Expand Down Expand Up @@ -100,6 +101,11 @@ const EXAMPLES = [

const NON_EMPTY_TEXT = /[^\s\u200B]/

const PROMPT_SUBMIT_ID = "prompt.submit"
const PROMPT_NEWLINE_ID = "prompt.newline"
const DEFAULT_PROMPT_SUBMIT_KEYBIND = "enter"
const DEFAULT_PROMPT_NEWLINE_KEYBIND = "shift+enter"

export const PromptInput: Component<PromptInputProps> = (props) => {
const sdk = useSDK()

Expand All @@ -112,9 +118,42 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const dialog = useDialog()
const providers = useProviders()
const command = useCommand()
const settings = useSettings()
const permission = usePermission()
const language = useLanguage()
const platform = usePlatform()

// Register Enter / Shift+Enter as rebindable commands so they surface in
// the Keyboard Shortcuts settings UI (grouped under "Prompt" via the
// "prompt." id prefix). The actual dispatch stays local in handleKeyDown
// below so the editor always wins over the global keymap.
//
// `disabled: true` excludes these from the global keymap in context/command
// so a focused non-editable target (e.g. a <button>) pressing Enter isn't
// silently swallowed by the global handler. The catalog entries remain
// visible in settings-keybinds.tsx because catalog population doesn't
// filter by disabled.
command.register(() => [
{
id: PROMPT_SUBMIT_ID,
title: language.t("command.prompt.submit"),
keybind: DEFAULT_PROMPT_SUBMIT_KEYBIND,
disabled: true,
},
{
id: PROMPT_NEWLINE_ID,
title: language.t("command.prompt.newline"),
keybind: DEFAULT_PROMPT_NEWLINE_KEYBIND,
Comment thread
CasualDeveloper marked this conversation as resolved.
disabled: true,
},
])

const submitKeybinds = createMemo(() =>
parseKeybind(settings.keybinds.get(PROMPT_SUBMIT_ID) ?? DEFAULT_PROMPT_SUBMIT_KEYBIND),
)
const newlineKeybinds = createMemo(() =>
parseKeybind(settings.keybinds.get(PROMPT_NEWLINE_ID) ?? DEFAULT_PROMPT_NEWLINE_KEYBIND),
)
const { params, tabs, view } = useSessionLayout()
let editorRef!: HTMLDivElement
let fileInputRef: HTMLInputElement | undefined
Expand Down Expand Up @@ -1165,11 +1204,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
}

// Handle Shift+Enter BEFORE IME check - Shift+Enter is never used for IME input
// and should always insert a newline regardless of composition state
if (event.key === "Enter" && event.shiftKey) {
// Handle configured newline keybind BEFORE the IME check - the default
// (Shift+Enter) is never used for IME input and should always insert a
// newline regardless of composition state. Users who rebind the newline
// keybind to unmodified Enter accept that this takes precedence over IME.
if (matchKeybind(newlineKeybinds(), event)) {
addPart({ type: "text", content: "\n", start: 0, end: 0 })
event.preventDefault()
event.stopPropagation()
return
}

Expand Down Expand Up @@ -1232,9 +1274,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}

// Note: Shift+Enter is handled earlier, before IME check
if (event.key === "Enter" && !event.shiftKey) {
// Note: the newline keybind is handled earlier, before the IME check.
if (matchKeybind(submitKeybinds(), event)) {
event.preventDefault()
event.stopPropagation()
if (event.repeat) return
if (
working() &&
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export const dict = {
"command.model.variant.cycle.description": "Switch to the next effort level",
"command.prompt.mode.shell": "Shell",
"command.prompt.mode.normal": "Prompt",
"command.prompt.submit": "Submit prompt",
"command.prompt.newline": "Insert newline in prompt",
"command.permissions.autoaccept.enable": "Auto-accept permissions",
"command.permissions.autoaccept.disable": "Stop auto-accepting permissions",
"command.workspace.toggle": "Toggle workspaces",
Expand Down
Loading