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
46 changes: 29 additions & 17 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { TuiEvent } from "./event"
import { KVProvider, useKV } from "./context/kv"
import { Provider } from "@/provider/provider"
import { ArgsProvider, useArgs, type Args } from "./context/args"
import { FormatError } from "@/cli/error"

async function getTerminalBackgroundColor(): Promise<"dark" | "light"> {
// can't set raw mode if not a TTY
Expand Down Expand Up @@ -468,12 +469,13 @@ function App() {

function ErrorComponent(props: { error: Error; reset: () => void; onExit: () => Promise<void> }) {
const term = useTerminalDimensions()
const [copied, setCopied] = createSignal(false)

useKeyboard((evt) => {
if (evt.ctrl && evt.name === "c") {
if ((evt.ctrl && evt.name === "c") || evt.name === "return") {
props.onExit()
}
})
const [copied, setCopied] = createSignal(false)

const issueURL = new URL("https://github.com/sst/opencode/issues/new?template=bug-report.yml")

Expand All @@ -496,28 +498,38 @@ function ErrorComponent(props: { error: Error; reset: () => void; onExit: () =>
})
}

const formattedError = FormatError(props.error) || props.error.message
const errorText = formattedError || props.error.stack || "An unknown error occurred"

return (
<box flexDirection="column" gap={1}>
<box flexDirection="row" gap={1} alignItems="center">
<text attributes={TextAttributes.BOLD}>Please report an issue.</text>
<box onMouseUp={copyIssueURL} backgroundColor="#565f89" padding={1}>
<text attributes={TextAttributes.BOLD}>Copy issue URL (exception info pre-filled)</text>
<box flexDirection="column" gap={1} padding={2}>
<text attributes={TextAttributes.BOLD} fg="#f7768e">
Fatal Error
</text>
<text fg="#c0caf5">A fatal error occurred. Please report this issue or try resetting the TUI:</text>
<box marginTop={1} flexDirection="row" gap={2} paddingBottom={1} alignItems="center" flexWrap="wrap">
<box onMouseDown={copyIssueURL} backgroundColor="#565f89" padding={1}>
<text attributes={copied() ? undefined : TextAttributes.BOLD}>
{copied() ? "Successfully copied" : "Copy issue URL (exception info pre-filled)"}
</text>
</box>
{copied() && <text>Successfully copied</text>}
</box>
<box flexDirection="row" gap={2} alignItems="center">
<text>A fatal error occurred!</text>
<box onMouseUp={props.reset} backgroundColor="#565f89" padding={1}>
<box onMouseDown={props.reset} backgroundColor="#565f89" padding={1}>
<text>Reset TUI</text>
</box>
<box onMouseUp={props.onExit} backgroundColor="#565f89" padding={1}>
</box>
<box flexDirection="row" gap={2} alignItems="center">
<box onMouseDown={props.onExit} backgroundColor="#565f89" padding={1}>
<text>Exit</text>
</box>
<box>
<text fg="#565f89">or press Ctrl+C / Enter</text>
</box>
</box>
<box marginTop={1}>
<scrollbox height={Math.floor(term().height * 0.7)}>
<text fg="#ff9e64">{errorText}</text>
</scrollbox>
</box>
<scrollbox height={Math.floor(term().height * 0.7)}>
<text>{props.error.stack}</text>
</scrollbox>
<text>{props.error.message}</text>
</box>
)
}
22 changes: 21 additions & 1 deletion packages/opencode/src/cli/cmd/tui/context/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { useRenderer } from "@opentui/solid"
import { createStore, produce } from "solid-js/store"
import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { NamedError } from "@/util/error"
import z from "zod"

type Theme = {
primary: RGBA
Expand Down Expand Up @@ -99,6 +101,22 @@ type ThemeJson = {
theme: Record<keyof Theme, ColorValue>
}

export namespace Theme {
export const ColorReferenceError = NamedError.create(
"ThemeColorReferenceError",
z.object({
color: z.string(),
}),
)

export const NotFoundError = NamedError.create(
"ThemeNotFoundError",
z.object({
theme: z.string(),
}),
)
}

export const DEFAULT_THEMES: Record<string, ThemeJson> = {
aura,
ayu,
Expand Down Expand Up @@ -140,7 +158,9 @@ function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
} else if (theme.theme[c as keyof Theme]) {
return resolveColor(theme.theme[c as keyof Theme])
} else {
throw new Error(`Color reference "${c}" not found in defs or theme`)
throw new Theme.ColorReferenceError({
color: c,
})
}
}
return resolveColor(c[mode])
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/src/cli/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConfigMarkdown } from "@/config/markdown"
import { Config } from "../config/config"
import { MCP } from "../mcp"
import { UI } from "./ui"
import { Theme } from "./cmd/tui/context/theme"

export function FormatError(input: unknown) {
if (MCP.Failed.isInstance(input))
Expand All @@ -23,5 +24,13 @@ export function FormatError(input: unknown) {
...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
].join("\n")

if (Theme.ColorReferenceError.isInstance(input)) {
return `Theme has an invalid color reference: "${input.data.color}"`
}

if (Theme.NotFoundError.isInstance(input)) {
return `Theme "${input.data.theme}" not found. Please check your theme configuration.`
}

if (UI.CancelledError.isInstance(input)) return ""
}