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
10 changes: 10 additions & 0 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DialogMcp } from "@tui/component/dialog-mcp"
import { DialogStatus } from "@tui/component/dialog-status"
import { DialogThemeList } from "@tui/component/dialog-theme-list"
import { DialogHelp } from "./ui/dialog-help"
import { DialogKeybindings } from "@tui/component/dialog-keybindings"
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
import { DialogAgent } from "@tui/component/dialog-agent"
import { DialogSessionList } from "@tui/component/dialog-session-list"
Expand Down Expand Up @@ -434,6 +435,15 @@ function App() {
},
category: "System",
},
{
title: "Show keybindings",
value: "help.keybindings",
keybind: "keybindings_list",
onSelect: () => {
dialog.replace(() => <DialogKeybindings />)
},
category: "System",
},
{
title: "Open docs",
value: "docs.open",
Expand Down
50 changes: 50 additions & 0 deletions packages/opencode/src/cli/cmd/tui/component/dialog-keybindings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createMemo } from "solid-js"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import { useDialog } from "@tui/ui/dialog"
import { useKeybind } from "@tui/context/keybind"
import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
import { zodToJsonSchema } from "zod-to-json-schema"
import { Config } from "@/config/config"

// Dynamically extract keybinding descriptions from the config schema
// This ensures descriptions stay in sync with the schema definitions
function getKeybindDescriptions(): Record<string, string> {
const jsonSchema = zodToJsonSchema(Config.Keybinds) as any
const descriptions: Record<string, string> = {}

for (const [key, schema] of Object.entries(jsonSchema.properties || {})) {
descriptions[key] = (schema as any).description || key
}

return descriptions
}

const KEYBIND_DESCRIPTIONS = getKeybindDescriptions()

export function DialogKeybindings() {
const keybind = useKeybind()
const dialog = useDialog()

// Get all keybindings and map them to options
const options = createMemo(() =>
Object.keys(keybind.all)
.filter((key) => key !== "leader") // Exclude the leader key itself
.map((key) => {
const typedKey = key as keyof KeybindsConfig
return {
title: KEYBIND_DESCRIPTIONS[typedKey] || key,
value: key,
footer: keybind.print(typedKey),
}
})
.sort((a, b) => a.title.localeCompare(b.title)), // Sort alphabetically by title
)

return (
<DialogSelect
title="Keybindings"
options={options()}
onSelect={() => dialog.clear()}
/>
)
}
1 change: 1 addition & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ export namespace Config {
model_cycle_favorite: z.string().optional().default("none").describe("Next favorite model"),
model_cycle_favorite_reverse: z.string().optional().default("none").describe("Previous favorite model"),
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
keybindings_list: z.string().optional().default("<leader>?").describe("Show all keybindings"),
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/js/src/v2/gen/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,10 @@ export type KeybindsConfig = {
* List available commands
*/
command_list?: string
/**
* Show all keybindings
*/
keybindings_list?: string
/**
* List agents
*/
Expand Down