flatten to keybind compatible config (#26421)

This commit is contained in:
Sebastian
2026-05-09 01:29:13 +02:00
committed by GitHub
parent 35deef6175
commit a0fc27e424
38 changed files with 1096 additions and 1518 deletions

View File

@@ -1,12 +1,11 @@
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import type { KeyEvent, Renderable } from "@opentui/core"
import type { Binding } from "@opentui/keymap"
import { resolveBindingSections, type BindingSectionsConfig } from "@opentui/keymap/extras"
import { createBindingLookup } from "@opentui/keymap/extras"
import { OpencodeClient, type Provider } from "@opencode-ai/sdk/v2"
import { TuiConfig, type Resolved } from "@/cli/cmd/tui/config/tui"
import { formatBindings } from "@/cli/cmd/run/keymap.shared"
import { KeymapSectionNames, keymapBindingDefaults, type KeymapSection } from "@/cli/cmd/tui/config/tui-schema"
import { ConfigKeybinds } from "@/config/keybinds"
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
import { resolveDiffStyle, resolveFooterKeybinds, resolveModelInfo } from "@/cli/cmd/run/runtime.boot"
type RunBinding = Binding<Renderable, KeyEvent>
@@ -82,34 +81,24 @@ function config(input?: {
}>
}): Resolved {
const bind = input?.bindings
const sections = {
global: Object.fromEntries([
...(bind?.commandList ? [["command.palette.show", bind.commandList] as const] : []),
...(bind?.variantCycle ? [["variant.cycle", bind.variantCycle] as const] : []),
]),
prompt: Object.fromEntries([
...(bind?.interrupt ? [["session.interrupt", bind.interrupt] as const] : []),
...(bind?.historyPrevious ? [["prompt.history.previous", bind.historyPrevious] as const] : []),
...(bind?.historyNext ? [["prompt.history.next", bind.historyNext] as const] : []),
...(bind?.inputClear ? [["prompt.clear", bind.inputClear] as const] : []),
]),
input: Object.fromEntries([
...(bind?.inputSubmit ? [["input.submit", bind.inputSubmit] as const] : []),
...(bind?.inputNewline ? [["input.newline", bind.inputNewline] as const] : []),
]),
} satisfies BindingSectionsConfig<Renderable, KeyEvent>
const keybinds = TuiKeybind.Keybinds.parse({
...(input?.leader && { leader: input.leader }),
...(bind?.commandList && { command_list: bind.commandList }),
...(bind?.variantCycle && { variant_cycle: bind.variantCycle }),
...(bind?.interrupt && { session_interrupt: bind.interrupt }),
...(bind?.historyPrevious && { history_previous: bind.historyPrevious }),
...(bind?.historyNext && { history_next: bind.historyNext }),
...(bind?.inputClear && { input_clear: bind.inputClear }),
...(bind?.inputSubmit && { input_submit: bind.inputSubmit }),
...(bind?.inputNewline && { input_newline: bind.inputNewline }),
})
return {
diff_style: input?.diff_style,
keybinds: ConfigKeybinds.Keybinds.parse({}),
keymap: {
leader: input?.leader ?? "ctrl+x",
leader_timeout: input?.leaderTimeout ?? 2000,
...resolveBindingSections<Renderable, KeyEvent, typeof sections, KeymapSection>(sections, {
sections: KeymapSectionNames,
bindingDefaults: keymapBindingDefaults,
}),
},
keybinds: createBindingLookup(TuiKeybind.toBindingConfig(keybinds), {
commandMap: TuiKeybind.CommandMap,
bindingDefaults: TuiKeybind.bindingDefaults(),
}),
leader_timeout: input?.leaderTimeout ?? 2000,
}
}
@@ -118,7 +107,7 @@ describe("run runtime boot", () => {
mock.restore()
})
test("reads footer keybinds from resolved keymap config", async () => {
test("reads footer keybinds from resolved keybind config", async () => {
spyOn(TuiConfig, "get").mockResolvedValue(
config({
leader: "ctrl+g",

View File

@@ -81,7 +81,7 @@ async function load(): Promise<Data> {
await Bun.write(
localPluginPath,
`import { resolveBindingSections } from "@opentui/keymap/extras"
`import { createBindingLookup } from "@opentui/keymap/extras"
import { useBindings } from "@opentui/keymap/solid"
export const ignored = async (_input, options) => {
@@ -97,20 +97,18 @@ export default {
const cfg_diff = api.tuiConfig.diff_style
const cfg_speed = api.tuiConfig.scroll_speed
const cfg_accel = api.tuiConfig.scroll_acceleration?.enabled
const cfg_submit = api.tuiConfig.keybinds?.input_submit
const has_keys = typeof api.keys.formatBindings === "function"
const keymap = resolveBindingSections(options.keymap?.sections ?? {
main: {
"plugin.loader.local": "ctrl+shift+m",
"plugin.loader.close": "escape",
},
}, { sections: ["main"] }).sections
const key_modal = keymap.main.find((item) => item.cmd === "plugin.loader.local")?.key
const key_close = keymap.main.find((item) => item.cmd === "plugin.loader.close")?.key
const keybinds = createBindingLookup(options.keybinds ?? {
"plugin.loader.local": "ctrl+shift+m",
"plugin.loader.close": "escape",
})
const bindings = keybinds.gather("plugin.loader", ["plugin.loader.local", "plugin.loader.close"])
const key_modal = bindings.find((item) => item.cmd === "plugin.loader.local")?.key
const key_close = bindings.find((item) => item.cmd === "plugin.loader.close")?.key
const key_unknown = "ctrl+k"
const off = api.keymap.registerLayer({
commands: [{ name: "plugin.loader.local", run() {} }, { name: "plugin.loader.close", run() {} }],
bindings: keymap.main,
bindings,
})
off()
const kv_before = api.kv.get(options.kv_key, "missing")
@@ -153,7 +151,7 @@ export default {
key_unknown,
has_keys,
has_keymap: typeof api.keymap.registerLayer === "function",
has_resolve_binding_sections: typeof resolveBindingSections === "function",
has_create_binding_lookup: typeof createBindingLookup === "function",
has_keymap_solid: typeof useBindings === "function",
kv_before,
kv_after,
@@ -176,7 +174,6 @@ export default {
cfg_diff,
cfg_speed,
cfg_accel,
cfg_submit,
}),
)
},
@@ -356,13 +353,9 @@ export default {
theme_name: tmp.extra.localThemeName,
kv_key: "plugin_state_key",
session_id: "ses_test",
keymap: {
sections: {
main: {
"plugin.loader.local": "ctrl+alt+m",
"plugin.loader.close": "q",
},
},
keybinds: {
"plugin.loader.local": "ctrl+alt+m",
"plugin.loader.close": "q",
},
}
const invalidOpts = {
@@ -408,9 +401,6 @@ export default {
diff_style: "stacked",
scroll_speed: 1.5,
scroll_acceleration: { enabled: true },
keybinds: {
input_submit: "ctrl+enter",
},
},
state: {
session: {
@@ -670,7 +660,7 @@ describe("tui.plugin.loader", () => {
expect(data.local.key_unknown).toBe("ctrl+k")
expect(data.local.has_keys).toBe(true)
expect(data.local.has_keymap).toBe(true)
expect(data.local.has_resolve_binding_sections).toBe(true)
expect(data.local.has_create_binding_lookup).toBe(true)
expect(data.local.has_keymap_solid).toBe(true)
expect(data.local.kv_before).toBe("missing")
expect(data.local.kv_after).toBe("stored")
@@ -693,7 +683,6 @@ describe("tui.plugin.loader", () => {
expect(data.local.cfg_diff).toBe("stacked")
expect(data.local.cfg_speed).toBe(1.5)
expect(data.local.cfg_accel).toBe(true)
expect(data.local.cfg_submit).toBe("ctrl+enter")
})
test("installs themes in the correct scope and remains resilient", () => {

View File

@@ -171,26 +171,26 @@ test("loads disabled-by-default internal plugin inactive and activates on demand
enabled: true,
active: true,
})
expect(TuiPluginRuntime.list().find((item) => item.id === "tui-which-key")).toEqual({
id: "tui-which-key",
expect(TuiPluginRuntime.list().find((item) => item.id === "which-key")).toEqual({
id: "which-key",
source: "internal",
spec: "tui-which-key",
target: "tui-which-key",
spec: "which-key",
target: "which-key",
enabled: false,
active: false,
})
await expect(TuiPluginRuntime.activatePlugin("tui-which-key")).resolves.toBe(true)
expect(TuiPluginRuntime.list().find((item) => item.id === "tui-which-key")).toEqual({
id: "tui-which-key",
await expect(TuiPluginRuntime.activatePlugin("which-key")).resolves.toBe(true)
expect(TuiPluginRuntime.list().find((item) => item.id === "which-key")).toEqual({
id: "which-key",
source: "internal",
spec: "tui-which-key",
target: "tui-which-key",
spec: "which-key",
target: "which-key",
enabled: true,
active: true,
})
expect(api.kv.get("plugin_enabled", {})).toEqual({
"tui-which-key": true,
"which-key": true,
})
} finally {
await TuiPluginRuntime.dispose()