flip theme type ownership

This commit is contained in:
Sebastian Herrlinger
2026-03-25 00:13:12 +01:00
parent ed0920b558
commit b178de68f0
3 changed files with 129 additions and 82 deletions
@@ -43,66 +43,12 @@ import { Global } from "@/global"
import { Filesystem } from "@/util/filesystem"
import { useTuiConfig } from "./tui-config"
import { isRecord } from "@/util/record"
import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui"
type ThemeColors = {
primary: RGBA
secondary: RGBA
accent: RGBA
error: RGBA
warning: RGBA
success: RGBA
info: RGBA
text: RGBA
textMuted: RGBA
selectedListItemText: RGBA
background: RGBA
backgroundPanel: RGBA
backgroundElement: RGBA
backgroundMenu: RGBA
border: RGBA
borderActive: RGBA
borderSubtle: RGBA
diffAdded: RGBA
diffRemoved: RGBA
diffContext: RGBA
diffHunkHeader: RGBA
diffHighlightAdded: RGBA
diffHighlightRemoved: RGBA
diffAddedBg: RGBA
diffRemovedBg: RGBA
diffContextBg: RGBA
diffLineNumber: RGBA
diffAddedLineNumberBg: RGBA
diffRemovedLineNumberBg: RGBA
markdownText: RGBA
markdownHeading: RGBA
markdownLink: RGBA
markdownLinkText: RGBA
markdownCode: RGBA
markdownBlockQuote: RGBA
markdownEmph: RGBA
markdownStrong: RGBA
markdownHorizontalRule: RGBA
markdownListItem: RGBA
markdownListEnumeration: RGBA
markdownImage: RGBA
markdownImageText: RGBA
markdownCodeBlock: RGBA
syntaxComment: RGBA
syntaxKeyword: RGBA
syntaxFunction: RGBA
syntaxVariable: RGBA
syntaxString: RGBA
syntaxNumber: RGBA
syntaxType: RGBA
syntaxOperator: RGBA
syntaxPunctuation: RGBA
}
type Theme = ThemeColors & {
type Theme = TuiThemeCurrent & {
_hasSelectedListItemText: boolean
thinkingOpacity: number
}
type ThemeColor = Exclude<keyof TuiThemeCurrent, "thinkingOpacity">
export function selectedForeground(theme: Theme, bg?: RGBA): RGBA {
// If theme explicitly defines selectedListItemText, use it
@@ -132,7 +78,7 @@ type ColorValue = HexColor | RefName | Variant | RGBA
export type ThemeJson = {
$schema?: string
defs?: Record<string, HexColor | RefName>
theme: Omit<Record<keyof ThemeColors, ColorValue>, "selectedListItemText" | "backgroundMenu"> & {
theme: Omit<Record<ThemeColor, ColorValue>, "selectedListItemText" | "backgroundMenu"> & {
selectedListItemText?: ColorValue
backgroundMenu?: ColorValue
thinkingOpacity?: number
@@ -250,7 +196,7 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
throw new Error(`Circular color reference: ${[...chain, c].join(" -> ")}`)
}
const next = defs[c] ?? theme.theme[c as keyof ThemeColors]
const next = defs[c] ?? theme.theme[c as ThemeColor]
if (next === undefined) {
throw new Error(`Color reference "${c}" not found in defs or theme`)
}
@@ -268,7 +214,7 @@ export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
.map(([key, value]) => {
return [key, resolveColor(value as ColorValue)]
}),
) as Partial<ThemeColors>
) as Partial<Record<ThemeColor, RGBA>>
// Handle selectedListItemText separately since it's optional
const hasSelectedListItemText = theme.theme.selectedListItemText !== undefined
+70 -13
View File
@@ -1,5 +1,5 @@
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import type { CliRenderer } from "@opentui/core"
import { RGBA, type CliRenderer } from "@opentui/core"
import { createPluginKeybind } from "../../src/cli/cmd/tui/context/plugin-keybinds"
import type { HostPluginApi } from "../../src/cli/cmd/tui/plugin/slots"
@@ -12,6 +12,74 @@ type Count = {
command_drop: number
}
function themeCurrent(): HostPluginApi["theme"]["current"] {
const a = RGBA.fromInts(0, 120, 240)
const b = RGBA.fromInts(120, 120, 120)
const c = RGBA.fromInts(230, 230, 230)
const d = RGBA.fromInts(120, 30, 30)
const e = RGBA.fromInts(140, 100, 40)
const f = RGBA.fromInts(20, 140, 80)
const g = RGBA.fromInts(20, 80, 160)
const h = RGBA.fromInts(40, 40, 40)
const i = RGBA.fromInts(60, 60, 60)
const j = RGBA.fromInts(80, 80, 80)
return {
primary: a,
secondary: b,
accent: a,
error: d,
warning: e,
success: f,
info: g,
text: c,
textMuted: b,
selectedListItemText: h,
background: h,
backgroundPanel: h,
backgroundElement: i,
backgroundMenu: i,
border: j,
borderActive: c,
borderSubtle: i,
diffAdded: f,
diffRemoved: d,
diffContext: b,
diffHunkHeader: b,
diffHighlightAdded: f,
diffHighlightRemoved: d,
diffAddedBg: h,
diffRemovedBg: h,
diffContextBg: h,
diffLineNumber: b,
diffAddedLineNumberBg: h,
diffRemovedLineNumberBg: h,
markdownText: c,
markdownHeading: c,
markdownLink: a,
markdownLinkText: g,
markdownCode: f,
markdownBlockQuote: e,
markdownEmph: e,
markdownStrong: c,
markdownHorizontalRule: b,
markdownListItem: a,
markdownListEnumeration: g,
markdownImage: a,
markdownImageText: g,
markdownCodeBlock: c,
syntaxComment: b,
syntaxKeyword: a,
syntaxFunction: g,
syntaxVariable: c,
syntaxString: f,
syntaxNumber: e,
syntaxType: a,
syntaxOperator: a,
syntaxPunctuation: c,
thinkingOpacity: 0.6,
}
}
type Opts = {
client?: HostPluginApi["client"]
renderer?: HostPluginApi["renderer"]
@@ -185,18 +253,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
},
theme: {
get current() {
return (
opts.theme?.current ?? {
text: "#ffffff",
textMuted: "#999999",
success: "#00ff00",
error: "#ff0000",
warning: "#ffff00",
backgroundElement: "#111111",
diffAdded: "#00ff00",
diffRemoved: "#ff0000",
}
)
return opts.theme?.current ?? themeCurrent()
},
get selected() {
return selected
+53 -9
View File
@@ -136,15 +136,59 @@ export type TuiToast = {
}
export type TuiThemeCurrent = {
readonly text: RGBA | string
readonly textMuted: RGBA | string
readonly success: RGBA | string
readonly error: RGBA | string
readonly warning: RGBA | string
readonly backgroundElement: RGBA | string
readonly diffAdded: RGBA | string
readonly diffRemoved: RGBA | string
readonly [key: string]: RGBA | string | number | boolean
readonly primary: RGBA
readonly secondary: RGBA
readonly accent: RGBA
readonly error: RGBA
readonly warning: RGBA
readonly success: RGBA
readonly info: RGBA
readonly text: RGBA
readonly textMuted: RGBA
readonly selectedListItemText: RGBA
readonly background: RGBA
readonly backgroundPanel: RGBA
readonly backgroundElement: RGBA
readonly backgroundMenu: RGBA
readonly border: RGBA
readonly borderActive: RGBA
readonly borderSubtle: RGBA
readonly diffAdded: RGBA
readonly diffRemoved: RGBA
readonly diffContext: RGBA
readonly diffHunkHeader: RGBA
readonly diffHighlightAdded: RGBA
readonly diffHighlightRemoved: RGBA
readonly diffAddedBg: RGBA
readonly diffRemovedBg: RGBA
readonly diffContextBg: RGBA
readonly diffLineNumber: RGBA
readonly diffAddedLineNumberBg: RGBA
readonly diffRemovedLineNumberBg: RGBA
readonly markdownText: RGBA
readonly markdownHeading: RGBA
readonly markdownLink: RGBA
readonly markdownLinkText: RGBA
readonly markdownCode: RGBA
readonly markdownBlockQuote: RGBA
readonly markdownEmph: RGBA
readonly markdownStrong: RGBA
readonly markdownHorizontalRule: RGBA
readonly markdownListItem: RGBA
readonly markdownListEnumeration: RGBA
readonly markdownImage: RGBA
readonly markdownImageText: RGBA
readonly markdownCodeBlock: RGBA
readonly syntaxComment: RGBA
readonly syntaxKeyword: RGBA
readonly syntaxFunction: RGBA
readonly syntaxVariable: RGBA
readonly syntaxString: RGBA
readonly syntaxNumber: RGBA
readonly syntaxType: RGBA
readonly syntaxOperator: RGBA
readonly syntaxPunctuation: RGBA
readonly thinkingOpacity: number
}
export type TuiTheme = {