summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax <[email protected]>2025-11-11 21:30:38 -0500
committerGitHub <[email protected]>2025-11-11 21:30:38 -0500
commitd81dce6a82d670d69e813a1bba67b9f06698d30b (patch)
tree43cac7183e558031e6e75c32d0cddf189ef7d3e8
parent0bd11e970b723b8cd59c14dd55e00829d9dcb862 (diff)
downloadopencode-d81dce6a82d670d69e813a1bba67b9f06698d30b.tar.gz
opencode-d81dce6a82d670d69e813a1bba67b9f06698d30b.zip
fix: add support for loading custom themes from .opencode/themes directory (#4229)
Co-authored-by: GitHub Action <[email protected]>
-rw-r--r--.opencode/themes/mytheme.json223
-rw-r--r--packages/opencode/src/cli/cmd/tui/context/theme.tsx47
-rw-r--r--packages/opencode/src/server/server.ts1
-rw-r--r--packages/plugin/package.json2
-rw-r--r--packages/sdk/js/package.json2
5 files changed, 270 insertions, 5 deletions
diff --git a/.opencode/themes/mytheme.json b/.opencode/themes/mytheme.json
new file mode 100644
index 000000000..e444de807
--- /dev/null
+++ b/.opencode/themes/mytheme.json
@@ -0,0 +1,223 @@
+{
+ "$schema": "https://opencode.ai/theme.json",
+ "defs": {
+ "nord0": "#2E3440",
+ "nord1": "#3B4252",
+ "nord2": "#434C5E",
+ "nord3": "#4C566A",
+ "nord4": "#D8DEE9",
+ "nord5": "#E5E9F0",
+ "nord6": "#ECEFF4",
+ "nord7": "#8FBCBB",
+ "nord8": "#88C0D0",
+ "nord9": "#81A1C1",
+ "nord10": "#5E81AC",
+ "nord11": "#BF616A",
+ "nord12": "#D08770",
+ "nord13": "#EBCB8B",
+ "nord14": "#A3BE8C",
+ "nord15": "#B48EAD"
+ },
+ "theme": {
+ "primary": {
+ "dark": "nord8",
+ "light": "nord10"
+ },
+ "secondary": {
+ "dark": "nord9",
+ "light": "nord9"
+ },
+ "accent": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "error": {
+ "dark": "nord11",
+ "light": "nord11"
+ },
+ "warning": {
+ "dark": "nord12",
+ "light": "nord12"
+ },
+ "success": {
+ "dark": "nord14",
+ "light": "nord14"
+ },
+ "info": {
+ "dark": "nord8",
+ "light": "nord10"
+ },
+ "text": {
+ "dark": "nord4",
+ "light": "nord0"
+ },
+ "textMuted": {
+ "dark": "nord3",
+ "light": "nord1"
+ },
+ "background": {
+ "dark": "nord0",
+ "light": "nord6"
+ },
+ "backgroundPanel": {
+ "dark": "nord1",
+ "light": "nord5"
+ },
+ "backgroundElement": {
+ "dark": "nord1",
+ "light": "nord4"
+ },
+ "border": {
+ "dark": "nord2",
+ "light": "nord3"
+ },
+ "borderActive": {
+ "dark": "nord3",
+ "light": "nord2"
+ },
+ "borderSubtle": {
+ "dark": "nord2",
+ "light": "nord3"
+ },
+ "diffAdded": {
+ "dark": "nord14",
+ "light": "nord14"
+ },
+ "diffRemoved": {
+ "dark": "nord11",
+ "light": "nord11"
+ },
+ "diffContext": {
+ "dark": "nord3",
+ "light": "nord3"
+ },
+ "diffHunkHeader": {
+ "dark": "nord3",
+ "light": "nord3"
+ },
+ "diffHighlightAdded": {
+ "dark": "nord14",
+ "light": "nord14"
+ },
+ "diffHighlightRemoved": {
+ "dark": "nord11",
+ "light": "nord11"
+ },
+ "diffAddedBg": {
+ "dark": "#3B4252",
+ "light": "#E5E9F0"
+ },
+ "diffRemovedBg": {
+ "dark": "#3B4252",
+ "light": "#E5E9F0"
+ },
+ "diffContextBg": {
+ "dark": "nord1",
+ "light": "nord5"
+ },
+ "diffLineNumber": {
+ "dark": "nord2",
+ "light": "nord4"
+ },
+ "diffAddedLineNumberBg": {
+ "dark": "#3B4252",
+ "light": "#E5E9F0"
+ },
+ "diffRemovedLineNumberBg": {
+ "dark": "#3B4252",
+ "light": "#E5E9F0"
+ },
+ "markdownText": {
+ "dark": "nord4",
+ "light": "nord0"
+ },
+ "markdownHeading": {
+ "dark": "nord8",
+ "light": "nord10"
+ },
+ "markdownLink": {
+ "dark": "nord9",
+ "light": "nord9"
+ },
+ "markdownLinkText": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "markdownCode": {
+ "dark": "nord14",
+ "light": "nord14"
+ },
+ "markdownBlockQuote": {
+ "dark": "nord3",
+ "light": "nord3"
+ },
+ "markdownEmph": {
+ "dark": "nord12",
+ "light": "nord12"
+ },
+ "markdownStrong": {
+ "dark": "nord13",
+ "light": "nord13"
+ },
+ "markdownHorizontalRule": {
+ "dark": "nord3",
+ "light": "nord3"
+ },
+ "markdownListItem": {
+ "dark": "nord8",
+ "light": "nord10"
+ },
+ "markdownListEnumeration": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "markdownImage": {
+ "dark": "nord9",
+ "light": "nord9"
+ },
+ "markdownImageText": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "markdownCodeBlock": {
+ "dark": "nord4",
+ "light": "nord0"
+ },
+ "syntaxComment": {
+ "dark": "nord3",
+ "light": "nord3"
+ },
+ "syntaxKeyword": {
+ "dark": "nord9",
+ "light": "nord9"
+ },
+ "syntaxFunction": {
+ "dark": "nord8",
+ "light": "nord8"
+ },
+ "syntaxVariable": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "syntaxString": {
+ "dark": "nord14",
+ "light": "nord14"
+ },
+ "syntaxNumber": {
+ "dark": "nord15",
+ "light": "nord15"
+ },
+ "syntaxType": {
+ "dark": "nord7",
+ "light": "nord7"
+ },
+ "syntaxOperator": {
+ "dark": "nord9",
+ "light": "nord9"
+ },
+ "syntaxPunctuation": {
+ "dark": "nord4",
+ "light": "nord0"
+ }
+ }
+}
diff --git a/packages/opencode/src/cli/cmd/tui/context/theme.tsx b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
index 74c2ff7f0..2a79620e6 100644
--- a/packages/opencode/src/cli/cmd/tui/context/theme.tsx
+++ b/packages/opencode/src/cli/cmd/tui/context/theme.tsx
@@ -1,5 +1,6 @@
import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
-import { createMemo } from "solid-js"
+import path from "path"
+import { createEffect, createMemo, onMount } from "solid-js"
import { useSync } from "@tui/context/sync"
import { createSimpleContext } from "./helper"
import aura from "./theme/aura.json" with { type: "json" }
@@ -27,7 +28,9 @@ import vesper from "./theme/vesper.json" with { type: "json" }
import zenburn from "./theme/zenburn.json" with { type: "json" }
import { useKV } from "./kv"
import { useRenderer } from "@opentui/solid"
-import { createStore } from "solid-js/store"
+import { createStore, produce } from "solid-js/store"
+import { Global } from "@/global"
+import { Filesystem } from "@/util/filesystem"
type Theme = {
primary: RGBA
@@ -144,6 +147,17 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
themes: DEFAULT_THEMES,
mode: props.mode,
active: (sync.data.config.theme ?? kv.get("theme", "opencode")) as string,
+ ready: false,
+ })
+
+ createEffect(async () => {
+ const custom = await getCustomThemes()
+ setStore(
+ produce((draft) => {
+ Object.assign(draft.themes, custom)
+ draft.ready = true
+ }),
+ )
})
const renderer = useRenderer()
@@ -187,12 +201,39 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
kv.set("theme", theme)
},
get ready() {
- return sync.ready
+ return store.ready
},
}
},
})
+const CUSTOM_THEME_GLOB = new Bun.Glob("themes/*.json")
+async function getCustomThemes() {
+ const directories = [
+ Global.Path.config,
+ ...(await Array.fromAsync(
+ Filesystem.up({
+ targets: [".opencode"],
+ start: process.cwd(),
+ }),
+ )),
+ ]
+
+ const result: Record<string, ThemeJson> = {}
+ for (const dir of directories) {
+ for await (const item of CUSTOM_THEME_GLOB.scan({
+ absolute: true,
+ followSymlinks: true,
+ dot: true,
+ cwd: dir,
+ })) {
+ const name = path.basename(item, ".json")
+ result[name] = await Bun.file(item).json()
+ }
+ }
+ return result
+}
+
function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJson {
const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 106693b22..2dee6c915 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -163,6 +163,7 @@ export namespace Server {
return c.json(await Config.get())
},
)
+
.patch(
"/config",
describeRoute({
diff --git a/packages/plugin/package.json b/packages/plugin/package.json
index 675971e2b..427ea3e40 100644
--- a/packages/plugin/package.json
+++ b/packages/plugin/package.json
@@ -24,4 +24,4 @@
"typescript": "catalog:",
"@typescript/native-preview": "catalog:"
}
-} \ No newline at end of file
+}
diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json
index 4e1b0df72..c4530dc65 100644
--- a/packages/sdk/js/package.json
+++ b/packages/sdk/js/package.json
@@ -26,4 +26,4 @@
"publishConfig": {
"directory": "dist"
}
-} \ No newline at end of file
+}