import { createStore } from "solid-js/store" import { createMemo, For, Match, Show, Switch } from "solid-js" import { useKeyboard, useTerminalDimensions, type JSX } from "@opentui/solid" import { useKeybind } from "../../context/keybind" import { useTheme } from "../../context/theme" import type { PermissionRequest } from "@opencode-ai/sdk/v2" import { useSDK } from "../../context/sdk" import { SplitBorder } from "../../component/border" import { useSync } from "../../context/sync" import path from "path" import { LANGUAGE_EXTENSIONS } from "@/lsp/language" import { Locale } from "@/util/locale" function normalizePath(input?: string) { if (!input) return "" if (path.isAbsolute(input)) { return path.relative(process.cwd(), input) || "." } return input } function filetype(input?: string) { if (!input) return "none" const ext = path.extname(input) const language = LANGUAGE_EXTENSIONS[ext] if (["typescriptreact", "javascriptreact", "javascript"].includes(language)) return "typescript" return language } function EditBody(props: { request: PermissionRequest }) { const { theme, syntax } = useTheme() const sync = useSync() const dimensions = useTerminalDimensions() const filepath = createMemo(() => (props.request.metadata?.filepath as string) ?? "") const diff = createMemo(() => (props.request.metadata?.diff as string) ?? "") const view = createMemo(() => { const diffStyle = sync.data.config.tui?.diff_style if (diffStyle === "stacked") return "unified" return dimensions().width > 120 ? "split" : "unified" }) const ft = createMemo(() => filetype(filepath())) return ( {"→"} Edit {normalizePath(filepath())} ) } function TextBody(props: { title: string; description?: string; icon?: string }) { const { theme } = useTheme() return ( <> {props.icon} {props.title} {props.description} ) } export function PermissionPrompt(props: { request: PermissionRequest }) { const sdk = useSDK() const sync = useSync() const [store, setStore] = createStore({ always: false, }) const input = createMemo(() => { const tool = props.request.tool if (!tool) return {} const parts = sync.data.part[tool.messageID] ?? [] for (const part of parts) { if (part.type === "tool" && part.callID === tool.callID && part.state.status !== "pending") { return part.state.input ?? {} } } return {} }) const { theme } = useTheme() return ( This will allow the following patterns until OpenCode is restarted {(pattern) => ( {"- "} {pattern} )} } options={{ confirm: "Confirm", cancel: "Cancel" }} escapeKey="cancel" onSelect={(option) => { setStore("always", false) if (option === "cancel") return sdk.client.permission.reply({ reply: "always", requestID: props.request.id, }) }} /> } options={{ once: "Allow once", always: "Allow always", reject: "Reject" }} escapeKey="reject" onSelect={(option) => { if (option === "always") { setStore("always", true) return } sdk.client.permission.reply({ reply: option as "once" | "reject", requestID: props.request.id, }) }} /> ) } function Prompt>(props: { title: string body: JSX.Element options: T escapeKey?: keyof T onSelect: (option: keyof T) => void }) { const { theme } = useTheme() const keybind = useKeybind() const keys = Object.keys(props.options) as (keyof T)[] const [store, setStore] = createStore({ selected: keys[0], }) useKeyboard((evt) => { if (evt.name === "left" || evt.name == "h") { evt.preventDefault() const idx = keys.indexOf(store.selected) const next = keys[(idx - 1 + keys.length) % keys.length] setStore("selected", next) } if (evt.name === "right" || evt.name == "l") { evt.preventDefault() const idx = keys.indexOf(store.selected) const next = keys[(idx + 1) % keys.length] setStore("selected", next) } if (evt.name === "return") { evt.preventDefault() props.onSelect(store.selected) } if (props.escapeKey && (evt.name === "escape" || keybind.match("app_exit", evt))) { evt.preventDefault() props.onSelect(props.escapeKey) } }) return ( {"△"} {props.title} {props.body} {(option) => ( {props.options[option]} )} {"⇆"} select enter confirm ) }