fix keymap fallback priority and TUI config diagnostics (#27384)
This commit is contained in:
@@ -3,9 +3,8 @@ export * as TuiConfig from "./tui"
|
||||
import path from "path"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { mergeDeep, unique } from "remeda"
|
||||
import { Context, Effect, Fiber, Layer, Schema } from "effect"
|
||||
import { Cause, Context, Effect, Fiber, Layer, Schema } from "effect"
|
||||
import { ConfigParse } from "@/config/parse"
|
||||
import { InvalidError } from "@/config/error"
|
||||
import * as ConfigPaths from "@/config/paths"
|
||||
import { migrateTuiConfig } from "./tui-migrate"
|
||||
import { KeymapLeaderTimeoutDefault, resolveAttentionSoundPaths, TuiInfo } from "./tui-schema"
|
||||
@@ -24,6 +23,7 @@ import { ConfigVariable } from "@/config/variable"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import type { DeepMutable } from "@opencode-ai/core/schema"
|
||||
import type { TuiAttentionSoundName } from "@opencode-ai/plugin/tui"
|
||||
import { FormatError, FormatUnknownError } from "@/cli/error"
|
||||
|
||||
const log = Log.create({ service: "tui.config" })
|
||||
|
||||
@@ -79,8 +79,26 @@ function normalize(raw: Record<string, unknown>) {
|
||||
}
|
||||
}
|
||||
|
||||
function dropUnknownKeybinds(input: Record<string, unknown>, configFilepath: string) {
|
||||
if (!isRecord(input.keybinds)) return input
|
||||
|
||||
const invalid = TuiKeybind.unknownKeys(input.keybinds)
|
||||
if (!invalid.length) return input
|
||||
|
||||
log.warn("ignored unknown tui keybinds", {
|
||||
path: configFilepath,
|
||||
keybinds: invalid,
|
||||
hint: "Remove these entries or rename them to keys from the tui.json schema.",
|
||||
})
|
||||
return {
|
||||
...input,
|
||||
keybinds: Object.fromEntries(Object.entries(input.keybinds).filter(([key]) => !invalid.includes(key))),
|
||||
}
|
||||
}
|
||||
|
||||
const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory: string }) {
|
||||
const afs = yield* AppFileSystem.Service
|
||||
let appliedOrder = 0
|
||||
|
||||
const resolvePlugins = (config: Info, configFilepath: string): Effect.Effect<Info> =>
|
||||
Effect.gen(function* () {
|
||||
@@ -101,16 +119,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
if (!isRecord(data)) return {} as Info
|
||||
// Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json
|
||||
// (mirroring the old opencode.json shape) still get their settings applied.
|
||||
const normalized = normalize(data)
|
||||
if (isRecord(normalized.keybinds)) {
|
||||
const invalid = TuiKeybind.unknownKeys(normalized.keybinds)
|
||||
if (invalid.length) {
|
||||
throw new InvalidError({
|
||||
path: configFilepath,
|
||||
message: `Unrecognized keybind${invalid.length === 1 ? "" : "s"}: ${invalid.join(", ")}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
const normalized = dropUnknownKeybinds(normalize(data), configFilepath)
|
||||
const parsed = ConfigParse.schema(Info, normalized, configFilepath)
|
||||
const validated = parsed.attention?.sounds
|
||||
? {
|
||||
@@ -127,7 +136,12 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
// can sync-throw — those become defects, which orElseSucceed wouldn't catch.
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.sync(() => {
|
||||
log.warn("invalid tui config", { path: configFilepath, cause })
|
||||
const error = Cause.squash(cause)
|
||||
const reason = FormatError(error) ?? FormatUnknownError(error)
|
||||
log.warn("skipping invalid tui config", {
|
||||
path: configFilepath,
|
||||
reason,
|
||||
})
|
||||
return {} as Info
|
||||
}),
|
||||
),
|
||||
@@ -141,18 +155,28 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
const text = yield* afs.readFileStringSafe(filepath).pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.sync(() => {
|
||||
log.warn("failed to read tui config", { path: filepath, cause })
|
||||
const error = Cause.squash(cause)
|
||||
const reason = FormatError(error) ?? FormatUnknownError(error)
|
||||
log.warn("failed to read tui config", {
|
||||
path: filepath,
|
||||
reason,
|
||||
})
|
||||
return undefined
|
||||
}),
|
||||
),
|
||||
)
|
||||
if (!text) return {} as Info
|
||||
log.info("loading tui config", { path: filepath })
|
||||
return yield* load(text, filepath)
|
||||
})
|
||||
|
||||
const mergeFile = (acc: Acc, file: string) =>
|
||||
Effect.gen(function* () {
|
||||
const data = yield* loadFile(file)
|
||||
if (Object.keys(data).length) {
|
||||
appliedOrder += 1
|
||||
log.info("applying tui config", { path: file, order: appliedOrder })
|
||||
}
|
||||
acc.result = mergeDeep(acc.result, data)
|
||||
if (!data.plugin?.length) return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user