mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
fix(editor): the editor color scheme follows a switch between light and dark themes (#1100)
Switching the IDE theme kept an explicitly chosen editor color scheme, so a light theme could keep a dark editor. When the applied theme turns light or dark, a chosen scheme of the other kind now switches back to the theme's own; a scheme picked to go with the current theme is kept. Reacts to the applied theme, so Quick Switch, Settings, Welcome › Customize, other windows and Sync with OS are all covered.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
/**
|
||||
* Applies Settings › Editor › Color Scheme (scheme + attribute overrides) as one
|
||||
* stylesheet, and Editor › Font › Typography Settings as the root's
|
||||
* `--fw-code` / `--fw-code-bold`.
|
||||
* `--fw-code` / `--fw-code-bold`. When the IDE theme turns light or dark, a chosen
|
||||
* scheme of the other kind is switched back to the theme's own.
|
||||
*/
|
||||
import { useProject } from "@/platform/project";
|
||||
import { editorPrefs, fontWeightVars } from "@/platform/editorSettings";
|
||||
import { schemeCss } from "@/platform/colorScheme";
|
||||
import { schemeAfterThemeChange, schemeCss } from "@/platform/colorScheme";
|
||||
import { call, commands, isTauri } from "@/platform/ipc";
|
||||
|
||||
export function installColorScheme(): () => void {
|
||||
if (typeof document === "undefined") return () => {};
|
||||
@@ -21,11 +23,36 @@ export function installColorScheme(): () => void {
|
||||
for (const [name, value] of Object.entries(fontWeightVars(prefs.fontWeight, prefs.boldFontWeight)))
|
||||
if (root.style.getPropertyValue(name) !== value) root.style.setProperty(name, value);
|
||||
};
|
||||
let kind = root.dataset.theme;
|
||||
const followTheme = () => {
|
||||
const next = root.dataset.theme;
|
||||
if (next === kind) return;
|
||||
const previous = kind;
|
||||
kind = next;
|
||||
const settings = useProject.getState().settings;
|
||||
if (!previous || !settings || (next !== "light" && next !== "dark")) return;
|
||||
const scheme = schemeAfterThemeChange(settings.editor?.colorScheme, next, editorPrefs(settings).customSchemes);
|
||||
if (scheme === null) return;
|
||||
if (!isTauri()) {
|
||||
useProject.setState({ settings: { ...settings, editor: { ...settings.editor, colorScheme: scheme } } });
|
||||
return;
|
||||
}
|
||||
call(commands.settingsSet("global", "editor.colorScheme", JSON.stringify(scheme))).then(
|
||||
(updated) => useProject.setState({ settings: updated }),
|
||||
(e) => console.warn("[color scheme] cannot follow the theme", e),
|
||||
);
|
||||
};
|
||||
apply();
|
||||
const unsubscribe = useProject.subscribe((s, prev) => {
|
||||
if (s.settings !== prev.settings) apply();
|
||||
});
|
||||
const observer = typeof MutationObserver !== "undefined" ? new MutationObserver(apply) : null;
|
||||
const observer =
|
||||
typeof MutationObserver !== "undefined"
|
||||
? new MutationObserver(() => {
|
||||
apply();
|
||||
followTheme();
|
||||
})
|
||||
: null;
|
||||
observer?.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme", "data-contrast", "data-color-theme", "data-plugin-theme"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { _setSchemeSource, schemeCss, schemeTokens } from "./colorScheme";
|
||||
import { _setSchemeSource, schemeAfterThemeChange, schemeCss, schemeTokens } from "./colorScheme";
|
||||
import { fontWeightVars, validateEditorExtras } from "./editorSettings";
|
||||
import type { Settings } from "./ipc";
|
||||
import { useProject } from "./project";
|
||||
@@ -66,6 +66,39 @@ describe("color schemes", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("switches a chosen scheme of the other kind back to the theme's own when the theme turns light or dark", async () => {
|
||||
expect(schemeAfterThemeChange("one-dark", "light")).toBe("theme-default");
|
||||
expect(schemeAfterThemeChange("solarized-light", "light")).toBeNull();
|
||||
expect(schemeAfterThemeChange("theme-default", "light")).toBeNull();
|
||||
const customs = { "custom-night": { name: "Night", parent: "dark" as const, attributes: {} } };
|
||||
expect(schemeAfterThemeChange("custom-night", "light", customs)).toBe("theme-default");
|
||||
expect(schemeAfterThemeChange("custom-night", "dark", customs)).toBeNull();
|
||||
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = "dark";
|
||||
const previous = useProject.getState().settings;
|
||||
const scheme = () => useProject.getState().settings?.editor?.colorScheme;
|
||||
useProject.setState({ settings: { editor: { colorScheme: "one-dark" } } as unknown as Settings });
|
||||
const dispose = installColorScheme();
|
||||
try {
|
||||
root.dataset.theme = "light";
|
||||
await Promise.resolve();
|
||||
expect(scheme()).toBe("theme-default");
|
||||
// A dark scheme chosen under the light theme stays until the theme changes again.
|
||||
useProject.setState({ settings: { editor: { colorScheme: "dark" } } as unknown as Settings });
|
||||
root.dataset.colorTheme = "solarized-light";
|
||||
await Promise.resolve();
|
||||
expect(scheme()).toBe("dark");
|
||||
root.dataset.theme = "dark";
|
||||
await Promise.resolve();
|
||||
expect(scheme()).toBe("dark");
|
||||
} finally {
|
||||
dispose();
|
||||
delete root.dataset.colorTheme;
|
||||
useProject.setState({ settings: previous });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps editor weights out of the schemes", () => {
|
||||
for (const id of ["light", "dark", "dark-hc"] as const) {
|
||||
expect(schemeTokens(id).has("--fw-code")).toBe(false);
|
||||
|
||||
@@ -259,6 +259,25 @@ export function effectiveScheme(
|
||||
return themeScheme(root);
|
||||
}
|
||||
|
||||
/** Light or dark: a built-in scheme's theme kind, a custom scheme's parent (null: unknown). */
|
||||
export function schemeKind(id: string | undefined, customs: CustomColorSchemes = {}): "light" | "dark" | null {
|
||||
if (isBuiltinTheme(id)) return themeKind(id);
|
||||
return (id && customSchemes(customs)[id]?.parent) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme setting once the IDE theme has turned `kind`, or null to keep it: a chosen scheme
|
||||
* of the other kind gives way to the theme's own, so a light theme doesn't keep a dark editor.
|
||||
*/
|
||||
export function schemeAfterThemeChange(
|
||||
setting: string | undefined,
|
||||
kind: "light" | "dark",
|
||||
customs: CustomColorSchemes = {},
|
||||
): string | null {
|
||||
const current = schemeKind(setting, customs);
|
||||
return current && current !== kind ? "theme-default" : null;
|
||||
}
|
||||
|
||||
/** Scheme belonging to the active IDE theme (`data-theme` / `data-contrast`). */
|
||||
export function themeScheme(
|
||||
root: HTMLElement | null = typeof document !== "undefined" ? document.documentElement : null,
|
||||
|
||||
Reference in New Issue
Block a user