дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
feat(themes): Solarized and One theme plugins; plugin themes from the first frame
Add two contribution-only theme plugins, each with a light and a dark theme that sets every UI token plus an editor colour scheme with syntax, editor and terminal ANSI colours: - plugins/theme-solarized: Solarized Light, Solarized Dark - plugins/theme-one: One Light, One Dark (Atom One, original HSL values) They replace the partial theme-solarized-dark sample. Each package ships the MIT notice of its palette in LICENSE, which is now copied on install with the files the manifest references. scripts/theme checks every theme plugin in plugins/ against the token list of tokens.css. Theme handling that any theme benefits from: - Start-up: boot.js restores the selected plugin theme's stylesheet and the last resolved frame, text and accent colours before any CSS loads, so the splash and workbench no longer show the built-in theme until the plugin host answers. The UI keeps the boot theme until the first plugin list, and the list now waits for the initial plugin load. - Native windows: the UI reports the plugin theme's resolved frame colour, which the plugin host persists and new project windows open in. - hsl(), color-mix() and var() tokens are resolved to RGB for Tauri's window background, the native Settings window (hex) and Mermaid diagrams. - Mermaid, the terminal and the editor colour scheme follow switches between plugin themes of the same light / dark base; an explicit editor scheme now replaces a plugin theme's editor colours. Supersedes #85. Co-authored-by: bghtyu <[email protected]> Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
1 джерело
866af3d9cb
коміт
b424cb32a8
43 files changed
+2035
-180
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
convertIcls,
|
||||
@@ -77,6 +77,73 @@ describe("theme plugin template", () => {
|
||||
});
|
||||
});
|
||||
|
||||
/** Theme packages in plugins/: every theme they contribute, with its editor scheme. */
|
||||
function pluginThemes() {
|
||||
return readdirSync("plugins", { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory() && existsSync(`plugins/${d.name}/plugin.toml`))
|
||||
.flatMap((d) => {
|
||||
const dir = `plugins/${d.name}`;
|
||||
const manifest = read(`${dir}/plugin.toml`);
|
||||
return manifest
|
||||
.split("[[contributes.themes]]")
|
||||
.slice(1)
|
||||
.map((entry) => ({
|
||||
dir,
|
||||
manifest,
|
||||
path: /^path\s*=\s*"([^"]+)"/m.exec(entry)?.[1],
|
||||
scheme: /^editor-scheme\s*=\s*"([^"]+)"/m.exec(entry)?.[1],
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
describe("theme plugins in plugins/", () => {
|
||||
const themes = pluginThemes();
|
||||
|
||||
it("include Solarized and One, light and dark", () => {
|
||||
expect(themes.map((t) => `${t.dir}/${t.path}`)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"plugins/theme-solarized/themes/solarized-light.theme.json",
|
||||
"plugins/theme-solarized/themes/solarized-dark.theme.json",
|
||||
"plugins/theme-one/themes/one-light.theme.json",
|
||||
"plugins/theme-one/themes/one-dark.theme.json",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it.each(themes.map((t) => [`${t.dir}/${t.path}`, t] as const))(
|
||||
"%s sets every UI token and its scheme every editor token",
|
||||
(_name, t) => {
|
||||
// A failure here means tokens.css gained (or lost) a themeable token: add it to the theme too.
|
||||
expect(t.path && t.scheme, "theme and editor-scheme paths").toBeTruthy();
|
||||
expect(t.manifest).not.toMatch(/^wasm\s*=/m);
|
||||
const ui = JSON.parse(stripJsonComments(read(`${t.dir}/${t.path}`))) as {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
tokens: Record<string, string>;
|
||||
};
|
||||
const ed = JSON.parse(stripJsonComments(read(`${t.dir}/${t.scheme}`))) as { tokens: Record<string, string> };
|
||||
expect(ui.id).toMatch(/^[a-z0-9][a-z0-9._-]*$/);
|
||||
expect(ui.name.trim()).not.toBe("");
|
||||
expect(["light", "dark"]).toContain(ui.kind);
|
||||
expect(sorted(Object.keys(ui.tokens))).toEqual(sorted(sets.ui));
|
||||
expect(sorted(Object.keys(ed.tokens))).toEqual(sorted(sets.scheme));
|
||||
for (const [k, v] of Object.entries({ ...ui.tokens, ...ed.tokens })) {
|
||||
expect(/[;{}<>\\\n]|url\(|image\(/i.test(v), `${k}: ${v}`).toBe(false);
|
||||
// var() may only point at tokens the IDE defines.
|
||||
for (const [, ref] of v.matchAll(/var\((--[a-z0-9-]+)/g)) expect(css, `${k} → ${ref}`).toContain(`${ref}:`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("ship the licence of the palettes they port", () => {
|
||||
for (const dir of new Set(themes.map((t) => t.dir))) {
|
||||
if (dir === "plugins/theme-solarized" || dir === "plugins/theme-one")
|
||||
expect(read(`${dir}/LICENSE`), dir).toContain("MIT License");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("IntelliJ theme import", () => {
|
||||
const themeJson = `{
|
||||
// comment
|
||||
|
||||
Reference in new issue
Заблокувати користувача