summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorSebastian Herrlinger <[email protected]>2025-12-11 17:20:04 +0100
committerSebastian Herrlinger <[email protected]>2025-12-11 17:23:47 +0100
commite271852bc3c6e3e4ee04a2245a7bc2c8f8b7f30a (patch)
treefe60fb286b13ccbdd3433a50793e3456b4120757 /packages
parent4e02704f17be406a7cec8b5a815582a08a2043cf (diff)
downloadopencode-e271852bc3c6e3e4ee04a2245a7bc2c8f8b7f30a.tar.gz
opencode-e271852bc3c6e3e4ee04a2245a7bc2c8f8b7f30a.zip
allow custom mappings for all textarea actions via config
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/package.json4
-rw-r--r--packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx72
-rw-r--r--packages/opencode/src/config/config.ts63
-rw-r--r--packages/web/src/content/docs/keybinds.mdx40
4 files changed, 157 insertions, 22 deletions
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index ec7365c42..ab08a043f 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -71,8 +71,8 @@
"@opencode-ai/sdk": "workspace:*",
"@opencode-ai/util": "workspace:*",
"@openrouter/ai-sdk-provider": "1.5.2",
- "@opentui/core": "0.1.60",
- "@opentui/solid": "0.1.60",
+ "@opentui/core": "0.0.0-20251211-4403a69a",
+ "@opentui/solid": "0.0.0-20251211-4403a69a",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
index fce9917c2..a5b6051ed 100644
--- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
@@ -46,6 +46,60 @@ export type PromptRef = {
const PLACEHOLDERS = ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"]
+const TEXTAREA_ACTIONS = [
+ "submit",
+ "newline",
+ "move-left",
+ "move-right",
+ "move-up",
+ "move-down",
+ "select-left",
+ "select-right",
+ "select-up",
+ "select-down",
+ "line-home",
+ "line-end",
+ "select-line-home",
+ "select-line-end",
+ "visual-line-home",
+ "visual-line-end",
+ "select-visual-line-home",
+ "select-visual-line-end",
+ "buffer-home",
+ "buffer-end",
+ "select-buffer-home",
+ "select-buffer-end",
+ "delete-line",
+ "delete-to-line-end",
+ "delete-to-line-start",
+ "backspace",
+ "delete",
+ "undo",
+ "redo",
+ "word-forward",
+ "word-backward",
+ "select-word-forward",
+ "select-word-backward",
+ "delete-word-forward",
+ "delete-word-backward",
+] as const
+
+function mapTextareaKeybindings(
+ keybinds: Record<string, { ctrl: boolean; meta: boolean; shift: boolean; leader: boolean; name: string }[]>,
+ action: (typeof TEXTAREA_ACTIONS)[number],
+): KeyBinding[] {
+ const configKey = `input_${action.replace(/-/g, "_")}`
+ const bindings = keybinds[configKey]
+ if (!bindings) return []
+ return bindings.map((binding) => ({
+ name: binding.name,
+ ctrl: binding.ctrl || undefined,
+ meta: binding.meta || undefined,
+ shift: binding.shift || undefined,
+ action,
+ }))
+}
+
export function Prompt(props: PromptProps) {
let input: TextareaRenderable
let anchor: BoxRenderable
@@ -76,26 +130,12 @@ export function Prompt(props: PromptProps) {
}
const textareaKeybindings = createMemo(() => {
- const newlineBindings = keybind.all.input_newline || []
- const submitBindings = keybind.all.input_submit || []
+ const keybinds = keybind.all
return [
{ name: "return", action: "submit" },
{ name: "return", meta: true, action: "newline" },
- ...newlineBindings.map((binding) => ({
- name: binding.name,
- ctrl: binding.ctrl || undefined,
- meta: binding.meta || undefined,
- shift: binding.shift || undefined,
- action: "newline" as const,
- })),
- ...submitBindings.map((binding) => ({
- name: binding.name,
- ctrl: binding.ctrl || undefined,
- meta: binding.meta || undefined,
- shift: binding.shift || undefined,
- action: "submit" as const,
- })),
+ ...TEXTAREA_ACTIONS.flatMap((action) => mapTextareaKeybindings(keybinds, action)),
] satisfies KeyBinding[]
})
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 34e3eea79..85f5b4409 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -466,7 +466,68 @@ export namespace Config {
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
input_submit: z.string().optional().default("return").describe("Submit input"),
- input_newline: z.string().optional().default("shift+return,ctrl+j").describe("Insert newline in input"),
+ input_newline: z
+ .string()
+ .optional()
+ .default("shift+return,ctrl+return,alt+return,ctrl+j")
+ .describe("Insert newline in input"),
+ input_move_left: z.string().optional().default("left,ctrl+b").describe("Move cursor left in input"),
+ input_move_right: z.string().optional().default("right,ctrl+f").describe("Move cursor right in input"),
+ input_move_up: z.string().optional().default("up").describe("Move cursor up in input"),
+ input_move_down: z.string().optional().default("down").describe("Move cursor down in input"),
+ input_select_left: z.string().optional().default("shift+left").describe("Select left in input"),
+ input_select_right: z.string().optional().default("shift+right").describe("Select right in input"),
+ input_select_up: z.string().optional().default("shift+up").describe("Select up in input"),
+ input_select_down: z.string().optional().default("shift+down").describe("Select down in input"),
+ input_line_home: z.string().optional().default("ctrl+a").describe("Move to start of line in input"),
+ input_line_end: z.string().optional().default("ctrl+e").describe("Move to end of line in input"),
+ input_select_line_home: z.string().optional().default("none").describe("Select to start of line in input"),
+ input_select_line_end: z.string().optional().default("none").describe("Select to end of line in input"),
+ input_visual_line_home: z.string().optional().default("none").describe("Move to start of visual line in input"),
+ input_visual_line_end: z.string().optional().default("none").describe("Move to end of visual line in input"),
+ input_select_visual_line_home: z
+ .string()
+ .optional()
+ .default("none")
+ .describe("Select to start of visual line in input"),
+ input_select_visual_line_end: z
+ .string()
+ .optional()
+ .default("none")
+ .describe("Select to end of visual line in input"),
+ input_buffer_home: z.string().optional().default("home").describe("Move to start of buffer in input"),
+ input_buffer_end: z.string().optional().default("end").describe("Move to end of buffer in input"),
+ input_select_buffer_home: z
+ .string()
+ .optional()
+ .default("shift+home")
+ .describe("Select to start of buffer in input"),
+ input_select_buffer_end: z.string().optional().default("shift+end").describe("Select to end of buffer in input"),
+ input_delete_line: z.string().optional().default("alt+d").describe("Delete line in input"),
+ input_delete_to_line_end: z.string().optional().default("ctrl+k").describe("Delete to end of line in input"),
+ input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
+ input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
+ input_delete: z.string().optional().default("delete,shift+delete").describe("Delete character in input"),
+ input_undo: z.string().optional().default("ctrl+-,cmd+z").describe("Undo in input"),
+ input_redo: z.string().optional().default("ctrl+.,cmd+shift+z").describe("Redo in input"),
+ input_word_forward: z.string().optional().default("alt+f,alt+right").describe("Move word forward in input"),
+ input_word_backward: z.string().optional().default("alt+b,alt+left").describe("Move word backward in input"),
+ input_select_word_forward: z
+ .string()
+ .optional()
+ .default("alt+shift+f,alt+shift+right")
+ .describe("Select word forward in input"),
+ input_select_word_backward: z
+ .string()
+ .optional()
+ .default("alt+shift+b,alt+shift+left")
+ .describe("Select word backward in input"),
+ input_delete_word_forward: z.string().optional().default("ctrl+d").describe("Delete word forward in input"),
+ input_delete_word_backward: z
+ .string()
+ .optional()
+ .default("ctrl+w,alt+backspace")
+ .describe("Delete word backward in input"),
history_previous: z.string().optional().default("up").describe("Previous history item"),
history_next: z.string().optional().default("down").describe("Next history item"),
session_child_cycle: z.string().optional().default("<leader>right").describe("Next child session"),
diff --git a/packages/web/src/content/docs/keybinds.mdx b/packages/web/src/content/docs/keybinds.mdx
index 36b51c68d..67e53c455 100644
--- a/packages/web/src/content/docs/keybinds.mdx
+++ b/packages/web/src/content/docs/keybinds.mdx
@@ -46,10 +46,44 @@ OpenCode has a list of keybinds that you can customize through the OpenCode conf
"agent_cycle_reverse": "shift+tab",
"input_clear": "ctrl+c",
"input_paste": "ctrl+v",
- "input_submit": "enter",
- "input_newline": "shift+enter,ctrl+j",
+ "input_submit": "return",
+ "input_newline": "shift+return,ctrl+return,alt+return,ctrl+j",
+ "input_move_left": "left,ctrl+b",
+ "input_move_right": "right,ctrl+f",
+ "input_move_up": "up",
+ "input_move_down": "down",
+ "input_select_left": "shift+left",
+ "input_select_right": "shift+right",
+ "input_select_up": "shift+up",
+ "input_select_down": "shift+down",
+ "input_line_home": "ctrl+a",
+ "input_line_end": "ctrl+e",
+ "input_select_line_home": "none",
+ "input_select_line_end": "none",
+ "input_visual_line_home": "none",
+ "input_visual_line_end": "none",
+ "input_select_visual_line_home": "none",
+ "input_select_visual_line_end": "none",
+ "input_buffer_home": "home",
+ "input_buffer_end": "end",
+ "input_select_buffer_home": "shift+home",
+ "input_select_buffer_end": "shift+end",
+ "input_delete_line": "alt+d",
+ "input_delete_to_line_end": "ctrl+k",
+ "input_delete_to_line_start": "ctrl+u",
+ "input_backspace": "backspace,shift+backspace",
+ "input_delete": "delete,shift+delete",
+ "input_undo": "ctrl+-,cmd+z",
+ "input_redo": "ctrl+.,cmd+shift+z",
+ "input_word_forward": "alt+f,alt+right",
+ "input_word_backward": "alt+b,alt+left",
+ "input_select_word_forward": "alt+shift+f,alt+shift+right",
+ "input_select_word_backward": "alt+shift+b,alt+shift+left",
+ "input_delete_word_forward": "ctrl+d",
+ "input_delete_word_backward": "ctrl+w,alt+backspace",
"history_previous": "up",
- "history_next": "down"
+ "history_next": "down",
+ "terminal_suspend": "ctrl+z"
}
}
```