Skip to content

Commit a0fc27e

Browse files
authored
flatten to keybind compatible config (#26421)
1 parent 35deef6 commit a0fc27e

38 files changed

Lines changed: 1081 additions & 1503 deletions

.opencode/plugins/tui-smoke.tsx

Lines changed: 89 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -2,87 +2,62 @@
22
import { useTerminalDimensions, type JSX } from "@opentui/solid"
33
import { useBindings, useKeymapSelector } from "@opentui/keymap/solid"
44
import { RGBA, VignetteEffect, type KeyEvent, type Renderable } from "@opentui/core"
5-
import { resolveBindingSections, type BindingSectionsConfig, type BindingValue } from "@opentui/keymap/extras"
6-
import type { Binding } from "@opentui/keymap"
5+
import { createBindingLookup, type BindingConfig } from "@opentui/keymap/extras"
76
import type { TuiPlugin, TuiPluginApi, TuiPluginMeta, TuiPluginModule, TuiSlotPlugin } from "@opencode-ai/plugin/tui"
87

98
const tabs = ["overview", "counter", "help"]
109
const command = {
11-
modal: "plugin.smoke.modal",
12-
screen: "plugin.smoke.screen",
13-
alert: "plugin.smoke.alert",
14-
confirm: "plugin.smoke.confirm",
15-
prompt: "plugin.smoke.prompt",
16-
select: "plugin.smoke.select",
17-
host: "plugin.smoke.host",
18-
home: "plugin.smoke.home",
19-
toast: "plugin.smoke.toast",
20-
dialog_close: "plugin.smoke.dialog.close",
21-
local_push: "plugin.smoke.local.push",
22-
local_pop: "plugin.smoke.local.pop",
23-
screen_home: "plugin.smoke.screen.home",
24-
screen_left: "plugin.smoke.screen.left",
25-
screen_right: "plugin.smoke.screen.right",
26-
screen_up: "plugin.smoke.screen.up",
27-
screen_down: "plugin.smoke.screen.down",
28-
screen_modal: "plugin.smoke.screen.modal",
29-
screen_local: "plugin.smoke.screen.local",
30-
screen_host: "plugin.smoke.screen.host",
31-
screen_alert: "plugin.smoke.screen.alert",
32-
screen_confirm: "plugin.smoke.screen.confirm",
33-
screen_prompt: "plugin.smoke.screen.prompt",
34-
screen_select: "plugin.smoke.screen.select",
35-
modal_accept: "plugin.smoke.modal.accept",
36-
modal_close: "plugin.smoke.modal.close",
37-
} as const
38-
39-
const sectionNames = ["global", "dialog", "local", "screen", "modal"] as const
40-
type SectionName = (typeof sectionNames)[number]
41-
type SectionConfig = Record<string, BindingValue<Renderable, KeyEvent>>
42-
type ResolvedSections = Record<SectionName, Binding<Renderable, KeyEvent>[]>
43-
type SmokeKeymap = {
44-
sections?: Partial<Record<SectionName, SectionConfig>>
10+
modal: "smoke_modal",
11+
screen: "smoke_screen",
12+
alert: "smoke_alert",
13+
confirm: "smoke_confirm",
14+
prompt: "smoke_prompt",
15+
select: "smoke_select",
16+
host: "smoke_host",
17+
home: "smoke_home",
18+
toast: "smoke_toast",
19+
dialog_close: "smoke_dialog_close",
20+
local_push: "smoke_local_push",
21+
local_pop: "smoke_local_pop",
22+
screen_home: "smoke_screen_home",
23+
screen_left: "smoke_screen_left",
24+
screen_right: "smoke_screen_right",
25+
screen_up: "smoke_screen_up",
26+
screen_down: "smoke_screen_down",
27+
screen_modal: "smoke_screen_modal",
28+
screen_local: "smoke_screen_local",
29+
screen_host: "smoke_screen_host",
30+
screen_alert: "smoke_screen_alert",
31+
screen_confirm: "smoke_screen_confirm",
32+
screen_prompt: "smoke_screen_prompt",
33+
screen_select: "smoke_screen_select",
34+
modal_accept: "smoke_modal_accept",
35+
modal_close: "smoke_modal_close",
4536
}
4637

47-
type SmokeOptions = {
48-
enabled?: boolean
49-
label?: unknown
50-
route?: unknown
51-
vignette?: unknown
52-
keymap?: SmokeKeymap
53-
}
38+
type SmokeBindings = BindingConfig<Renderable, KeyEvent>
5439

5540
const defaultKeymap = {
56-
global: {
57-
[command.modal]: "ctrl+shift+m",
58-
[command.screen]: "ctrl+shift+o",
59-
},
60-
dialog: {
61-
[command.dialog_close]: "escape",
62-
},
63-
local: {
64-
[command.local_push]: "enter,return",
65-
[command.local_pop]: "escape,q,backspace",
66-
},
67-
screen: {
68-
[command.screen_home]: "escape,ctrl+h",
69-
[command.screen_left]: "left,h",
70-
[command.screen_right]: "right,l",
71-
[command.screen_up]: "up,k",
72-
[command.screen_down]: "down,j",
73-
[command.screen_modal]: "ctrl+shift+m",
74-
[command.screen_local]: "x",
75-
[command.screen_host]: "z",
76-
[command.screen_alert]: "a",
77-
[command.screen_confirm]: "c",
78-
[command.screen_prompt]: "p",
79-
[command.screen_select]: "s",
80-
},
81-
modal: {
82-
[command.modal_accept]: "enter,return",
83-
[command.modal_close]: "escape",
84-
},
85-
} satisfies Record<SectionName, SectionConfig>
41+
[command.modal]: "ctrl+shift+m",
42+
[command.screen]: "ctrl+shift+o",
43+
[command.dialog_close]: "escape",
44+
[command.local_push]: "enter,return",
45+
[command.local_pop]: "escape,q,backspace",
46+
[command.screen_home]: "escape,ctrl+h",
47+
[command.screen_left]: "left,h",
48+
[command.screen_right]: "right,l",
49+
[command.screen_up]: "up,k",
50+
[command.screen_down]: "down,j",
51+
[command.screen_modal]: "ctrl+shift+m",
52+
[command.screen_local]: "x",
53+
[command.screen_host]: "z",
54+
[command.screen_alert]: "a",
55+
[command.screen_confirm]: "c",
56+
[command.screen_prompt]: "p",
57+
[command.screen_select]: "s",
58+
[command.modal_accept]: "enter,return",
59+
[command.modal_close]: "escape",
60+
}
8661

8762
const pick = (value: unknown, fallback: string) => {
8863
if (typeof value !== "string") return fallback
@@ -95,11 +70,14 @@ const num = (value: unknown, fallback: number) => {
9570
return value
9671
}
9772

73+
const record = (value: unknown): value is Record<string, unknown> =>
74+
!!value && typeof value === "object" && !Array.isArray(value)
75+
9876
type Cfg = {
9977
label: string
10078
route: string
10179
vignette: number
102-
keymap: SmokeKeymap | undefined
80+
keybinds: SmokeBindings | undefined
10381
}
10482

10583
type Route = {
@@ -116,12 +94,12 @@ type State = {
11694
local: number
11795
}
11896

119-
const cfg = (options: SmokeOptions | undefined) => {
97+
const cfg = (options: Record<string, unknown> | undefined) => {
12098
return {
12199
label: pick(options?.label, "smoke"),
122100
route: pick(options?.route, "workspace-smoke"),
123101
vignette: Math.max(0, num(options?.vignette, 0.35)),
124-
keymap: options?.keymap,
102+
keybinds: record(options?.keybinds) ? (options.keybinds as SmokeBindings) : undefined,
125103
}
126104
}
127105

@@ -132,21 +110,8 @@ const names = (input: Cfg) => {
132110
}
133111
}
134112

135-
function createKeys(input: SmokeKeymap | undefined): { sections: ResolvedSections } {
136-
const sections = resolveBindingSections(
137-
{
138-
global: { ...defaultKeymap.global, ...input?.sections?.global },
139-
dialog: { ...defaultKeymap.dialog, ...input?.sections?.dialog },
140-
local: { ...defaultKeymap.local, ...input?.sections?.local },
141-
screen: { ...defaultKeymap.screen, ...input?.sections?.screen },
142-
modal: { ...defaultKeymap.modal, ...input?.sections?.modal },
143-
} satisfies BindingSectionsConfig<Renderable, KeyEvent>,
144-
{ sections: sectionNames },
145-
).sections
146-
147-
return {
148-
sections,
149-
}
113+
function createKeys(input: SmokeBindings | undefined) {
114+
return createBindingLookup({ ...defaultKeymap, ...input })
150115
}
151116

152117
type Keys = ReturnType<typeof createKeys>
@@ -376,7 +341,7 @@ const Screen = (props: {
376341
},
377342
},
378343
],
379-
bindings: props.keys.sections.dialog,
344+
bindings: props.keys.gather("smoke.dialog", [command.dialog_close]),
380345
}))
381346

382347
useBindings(() => ({
@@ -395,7 +360,7 @@ const Screen = (props: {
395360
},
396361
},
397362
],
398-
bindings: props.keys.sections.local,
363+
bindings: props.keys.gather("smoke.local", [command.local_push, command.local_pop]),
399364
}))
400365

401366
useBindings(() => ({
@@ -478,7 +443,20 @@ const Screen = (props: {
478443
},
479444
},
480445
],
481-
bindings: props.keys.sections.screen,
446+
bindings: props.keys.gather("smoke.screen", [
447+
command.screen_home,
448+
command.screen_left,
449+
command.screen_right,
450+
command.screen_up,
451+
command.screen_down,
452+
command.screen_modal,
453+
command.screen_local,
454+
command.screen_host,
455+
command.screen_alert,
456+
command.screen_confirm,
457+
command.screen_prompt,
458+
command.screen_select,
459+
]),
482460
}))
483461
const shortcuts = useKeymapSelector((keymap) => {
484462
const bindings = keymap.getCommandBindings({
@@ -687,7 +665,7 @@ const Modal = (props: {
687665
},
688666
},
689667
],
690-
bindings: props.keys.sections.modal,
668+
bindings: props.keys.gather("smoke.modal", [command.modal_accept, command.modal_close]),
691669
}))
692670
const shortcuts = useKeymapSelector((keymap) => {
693671
const bindings = keymap.getCommandBindings({
@@ -766,25 +744,8 @@ const home = (api: TuiPluginApi, input: Cfg) => ({
766744
},
767745
home_prompt(ctx, value) {
768746
const skin = look(ctx.theme.current)
769-
type Prompt = (props: {
770-
workspaceID?: string
771-
visible?: boolean
772-
disabled?: boolean
773-
onSubmit?: () => void
774-
hint?: JSX.Element
775-
right?: JSX.Element
776-
showPlaceholder?: boolean
777-
placeholders?: {
778-
normal?: string[]
779-
shell?: string[]
780-
}
781-
}) => JSX.Element
782-
type Slot = (
783-
props: { name: string; mode?: unknown; children?: JSX.Element } & Record<string, unknown>,
784-
) => JSX.Element | null
785-
const ui = api.ui as TuiPluginApi["ui"] & { Prompt: Prompt; Slot: Slot }
786-
const Prompt = ui.Prompt
787-
const Slot = ui.Slot
747+
const Prompt = api.ui.Prompt
748+
const Slot = api.ui.Slot
788749
const normal = [
789750
`[SMOKE] route check for ${input.label}`,
790751
"[SMOKE] confirm home_prompt slot override",
@@ -1003,20 +964,29 @@ const reg = (api: TuiPluginApi, input: Cfg, keys: Keys) => {
1003964
},
1004965
},
1005966
],
1006-
bindings: keys.sections.global,
967+
bindings: keys.gather("smoke.global", [
968+
command.modal,
969+
command.screen,
970+
command.alert,
971+
command.confirm,
972+
command.prompt,
973+
command.select,
974+
command.host,
975+
command.home,
976+
command.toast,
977+
]),
1007978
})
1008979
}
1009980

1010981
const tui: TuiPlugin = async (api, options, meta) => {
1011-
const input = options as SmokeOptions | undefined
1012-
if (input?.enabled === false) return
982+
if (options?.enabled === false) return
1013983

1014984
await api.theme.install("./smoke-theme.json")
1015985
api.theme.set("smoke-theme")
1016986

1017-
const value = cfg(input)
987+
const value = cfg(options)
1018988
const route = names(value)
1019-
const keys = createKeys(value.keymap)
989+
const keys = createKeys(value.keybinds)
1020990
const fx = new VignetteEffect(value.vignette)
1021991
const post = fx.apply.bind(fx)
1022992
api.renderer.addPostProcessFn(post)

.opencode/tui.json

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,12 @@
66
{
77
"enabled": false,
88
"label": "workspace",
9-
"keymap": {
10-
"sections": {
11-
"global": {
12-
"plugin.smoke.modal": "ctrl+alt+m",
13-
"plugin.smoke.screen": "ctrl+alt+o"
14-
},
15-
"screen": {
16-
"plugin.smoke.screen.home": "escape,ctrl+shift+h",
17-
"plugin.smoke.screen.modal": "ctrl+alt+m"
18-
},
19-
"dialog": {
20-
"plugin.smoke.dialog.close": "escape,q"
21-
}
22-
}
9+
"keybinds": {
10+
"smoke_modal": "ctrl+alt+m",
11+
"smoke_screen": "ctrl+alt+o",
12+
"smoke_screen_home": "escape,ctrl+shift+h",
13+
"smoke_screen_modal": "ctrl+alt+m",
14+
"smoke_dialog_close": "escape,q"
2315
}
2416
}
2517
]

bun.lock

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)