返回 DeepSeek-Reasonix
terminalTheme.ts
根目录 / desktop / frontend / src / lib / terminalTheme.ts
1 import type { ITheme } from "@xterm/xterm";
2
3 import { getResolvedTheme, type ResolvedTheme } from "./theme";
4
5 export type TerminalThemePreference = "auto" | "dark" | "light";
6
7 const AUTO_THEME_MEDIA_QUERY = "(prefers-color-scheme: light)";
8 let currentTerminalTheme: TerminalThemePreference = "auto";
9 const listeners = new Set<() => void>();
10
11 const darkANSI: ITheme = {
12 black: "#24272b",
13 red: "#ff6b6b",
14 green: "#89d185",
15 yellow: "#f2cc60",
16 blue: "#6ca4f8",
17 magenta: "#c58af9",
18 cyan: "#56d4dd",
19 white: "#d8d8d8",
20 brightBlack: "#70777f",
21 brightRed: "#ff8787",
22 brightGreen: "#a6e3a1",
23 brightYellow: "#ffe08a",
24 brightBlue: "#8ab4f8",
25 brightMagenta: "#d7a7ff",
26 brightCyan: "#79e2e8",
27 brightWhite: "#ffffff",
28 };
29
30 const lightANSI: ITheme = {
31 black: "#25272a",
32 red: "#b4232d",
33 green: "#3f6f16",
34 yellow: "#8a5700",
35 blue: "#1d5fbf",
36 magenta: "#7c3aaa",
37 cyan: "#08746f",
38 white: "#555b61",
39 brightBlack: "#6b7077",
40 brightRed: "#c3343f",
41 brightGreen: "#4b7a1f",
42 brightYellow: "#986000",
43 brightBlue: "#2c6cc5",
44 brightMagenta: "#9751c6",
45 brightCyan: "#0e7d77",
46 brightWhite: "#34383d",
47 };
48
49 // Wails dispatches bound Go calls on separate goroutines. Keep terminal-theme
50 // writes in click order so an older save can never finish after newer intent.
51 export function createTerminalThemeSaveQueue(
52 persist: (theme: TerminalThemePreference) => Promise<void>,
53 ): (theme: TerminalThemePreference) => Promise<void> {
54 let tail = Promise.resolve();
55 return (theme) => {
56 const save = tail.then(() => persist(theme));
57 tail = save.catch(() => undefined);
58 return save;
59 };
60 }
61
62 export function normalizeTerminalThemePreference(value: unknown): TerminalThemePreference {
63 return value === "dark" || value === "light" ? value : "auto";
64 }
65
66 export function getTerminalThemePreference(): TerminalThemePreference {
67 return currentTerminalTheme;
68 }
69
70 export function getResolvedTerminalTheme(
71 preference: TerminalThemePreference = getTerminalThemePreference(),
72 ): ResolvedTheme {
73 return preference === "auto" ? getResolvedTheme() : preference;
74 }
75
76 export function applyTerminalThemePreference(value: unknown): TerminalThemePreference {
77 const next = normalizeTerminalThemePreference(value);
78 currentTerminalTheme = next;
79 if (typeof document !== "undefined") {
80 if (next === "auto") document.documentElement.removeAttribute("data-terminal-theme");
81 else document.documentElement.setAttribute("data-terminal-theme", next);
82 }
83 for (const listener of listeners) listener();
84 return next;
85 }
86
87 export function onTerminalThemePreferenceChange(listener: () => void): () => void {
88 listeners.add(listener);
89 return () => listeners.delete(listener);
90 }
91
92 function cssToken(style: CSSStyleDeclaration, name: string, fallback: string): string {
93 return style.getPropertyValue(name).trim() || fallback;
94 }
95
96 export function terminalThemeForElement(element: Element): ITheme {
97 const resolved = getResolvedTerminalTheme();
98 const palette = resolved === "light" ? lightANSI : darkANSI;
99 const style = getComputedStyle(element);
100 return {
101 ...palette,
102 background: cssToken(style, "--terminal-bg", resolved === "light" ? "#f7f8fa" : "#111315"),
103 foreground: cssToken(style, "--terminal-fg", resolved === "light" ? "#25272a" : "#e8e5df"),
104 cursor: cssToken(style, "--terminal-cursor", resolved === "light" ? "#9a4f00" : "#e6a15c"),
105 cursorAccent: cssToken(style, "--terminal-bg", resolved === "light" ? "#f7f8fa" : "#111315"),
106 selectionBackground: cssToken(style, "--terminal-selection", resolved === "light" ? "#b9d7ff" : "#344c68"),
107 };
108 }
109
110 // Watch both explicit app-theme mutations and OS theme changes. Theme packs can
111 // replace root CSS variables without changing the terminal preference itself,
112 // so the root style attribute is observed as well.
113 export function observeTerminalTheme(element: Element, listener: () => void): () => void {
114 const unsubscribe = onTerminalThemePreferenceChange(listener);
115 const root = typeof document !== "undefined" ? document.documentElement : null;
116 const observer = root && typeof MutationObserver !== "undefined"
117 ? new MutationObserver(listener)
118 : null;
119 observer?.observe(root!, {
120 attributes: true,
121 attributeFilter: ["data-theme", "data-theme-style", "data-terminal-theme", "style"],
122 });
123
124 const media = typeof window !== "undefined" && window.matchMedia
125 ? window.matchMedia(AUTO_THEME_MEDIA_QUERY)
126 : null;
127 media?.addEventListener?.("change", listener);
128 void element;
129
130 return () => {
131 unsubscribe();
132 observer?.disconnect();
133 media?.removeEventListener?.("change", listener);
134 };
135 }
136
136 lines TYPESCRIPT