fix(themes): match IntelliJ's gutter, selected tab border and completion colours on import (#1424)

Themes imported from IntelliJ drew a lighter gutter strip and kept the built-in blue for the selected tab's border and completion matches. The converter read the classic UI's GUTTER_BACKGROUND, where the new UI paints EDITOR_GUTTER_BACKGROUND or else the editor's background; it read EditorTabs.underlineColor, where Islands borders the selected tab with underlinedBorderColor; and it didn't map CompletionPopup.matchForeground. Scheme attributes written as `<value />` (no colours, not inherited) now show as plain text instead of keeping the built-in colours.

Co-authored-by: ldg030201 <[email protected]>
Цей коміт міститься в:
Lee Dong Geon authored and GitHub committed 2026-11-05 10:48:21 +08:00
1 джерело 38ac62f1a4
коміт 001381459e
3 files changed
+44 -10

No files matched your search

+7 -2
Переглянути файл
@@ -192,7 +192,9 @@ ship no `.icls` at all keep the built-in syntax colours.
| `--danger`, `--warning` | `Component.errorFocusColor`, `Component.warningFocusColor` |
| `--bg-tooltip`, `--fg-tooltip`, `--border-tooltip` | `ToolTip.background`, `.foreground`, `.borderColor` |
| `--bg-button`, `--border-button` | `Button.startBackground`, `Button.startBorderColor` |
| `--bg-tab-selected`, `--border-tab-selected` | `EditorTabs.underlinedTabBackground`, `EditorTabs.underlineColor` |
| `--bg-tab-selected` | `EditorTabs.underlinedTabBackground` |
| `--border-tab-selected` | `EditorTabs.underlinedBorderColor` (Islands), `EditorTabs.underlineColor` |
| `--fg-completion-match` | `CompletionPopup.matchForeground` |
| `--bg-toolbar-hover`, `--bg-toolbar-pressed` | `ActionButton.hoverBackground`, `ActionButton.pressedBackground` |
| `--bg-stripe-hover`, `--bg-stripe-selected` | `ToolWindow.Button.hoverBackground`, `.selectedBackground` |
| `--scrollbar-thumb`, `--scrollbar-thumb-hover` | `ScrollBar.Mac.thumbColor`, `ScrollBar.Mac.hoverThumbColor` |
@@ -209,6 +211,7 @@ The full table is `UI_KEYS` in [`src/features/plugins/intellijTheme.ts`](../../s
| `--caret`, `--bg-current-line` | colour `CARET_COLOR`, `CARET_ROW_COLOR` |
| `--bg-editor-selection` | colour `SELECTION_BACKGROUND` |
| `--fg-line-number`, `--fg-line-number-current` | colour `LINE_NUMBERS_COLOR`, `LINE_NUMBER_ON_CARET_ROW_COLOR` |
| `--bg-editor-gutter` | colour `EDITOR_GUTTER_BACKGROUND`, else the editor background (as the new UI paints it; `GUTTER_BACKGROUND` is the classic UI's) |
| `--border-indent-guide`, `--border-right-margin`, `--fg-whitespace` | colour `INDENT_GUIDE`, `RIGHT_MARGIN_COLOR`, `WHITESPACES` |
| `--vcs-*`, `--gutter-*` | colour `FILESTATUS_*`, `MODIFIED_LINES_COLOR`, `ADDED_LINES_COLOR`, `DELETED_LINES_COLOR` |
| `--syn-keyword`, `--syn-string`, `--syn-number` | `DEFAULT_KEYWORD`, `DEFAULT_STRING`, `DEFAULT_NUMBER` |
@@ -226,7 +229,9 @@ The full table is `UI_KEYS` in [`src/features/plugins/intellijTheme.ts`](../../s
| `--syn-self`, `--syn-keyword-arg`, `--syn-docstring`, `--syn-predefined` | `PY.SELF_PARAMETER`, `PY.KEYWORD_ARGUMENT`, `PY.DOC_COMMENT`, `PY.BUILTIN_NAME` |
| `--ansi-*` | `CONSOLE_*_OUTPUT` (`CONSOLE_DARKGRAY_OUTPUT` is bright black, `CONSOLE_GRAY_OUTPUT` white) |
The full tables are `ICLS_COLORS` and `ICLS_ATTRIBUTES` in the same file.
The full tables are `ICLS_COLORS` and `ICLS_ATTRIBUTES` in the same file. An
attribute written as `<value />` has no colours of its own and shows as plain
text (`TEXT`'s foreground), as in IntelliJ, not with the parent scheme's colour.
### From VS Code (by hand)
+12
Переглянути файл
@@ -91,6 +91,8 @@ describe("IntelliJ theme import", () => {
"Button": { "default": { "startBackground": "accent" } },
"Popup.background": "#181f28",
"Label.infoForeground": "#80888F80",
"EditorTabs": { "underlineColor": "#405060", "underlinedBorderColor": "#bd93f9" },
"CompletionPopup.matchForeground": "#ff79c6",
"Tree": { "rowHeight": 24 }
}
}`;
@@ -99,6 +101,7 @@ describe("IntelliJ theme import", () => {
<option name="CARET_COLOR" value="ffcc00" />
<option name="LINE_NUMBERS_COLOR" value="0" />
<option name="UNKNOWN_KEY" value="123456" />
<option name="GUTTER_BACKGROUND" value="313335" />
</colors>
<attributes>
<option name="DEFAULT_KEYWORD">
@@ -126,6 +129,9 @@ describe("IntelliJ theme import", () => {
</value>
</option>
<option name="DEFAULT_CONSTANT" baseAttributes="DEFAULT_IDENTIFIER" />
<option name="DEFAULT_INSTANCE_FIELD">
<value />
</option>
</attributes>
</scheme>`;
@@ -138,6 +144,9 @@ describe("IntelliJ theme import", () => {
expect(t.tokens["--bg-selected"]).toBe("#203040");
expect(t.tokens["--fg-muted"]).toBe("#80888f80");
expect(t.tokens["--fg"]).toBe("#c0c8d0");
// Islands borders the selected tab with underlinedBorderColor.
expect(t.tokens["--border-tab-selected"]).toBe("#bd93f9");
expect(t.tokens["--fg-completion-match"]).toBe("#ff79c6");
expect(s).toBeNull();
});
@@ -165,9 +174,12 @@ describe("IntelliJ theme import", () => {
"--syn-comment-style": "italic",
"--fg-editor": "#a9b7c6",
"--bg-editor": "#2b2b2b",
// GUTTER_BACKGROUND is the classic UI's: the gutter keeps the editor's background.
"--bg-editor-gutter": "#2b2b2b",
"--wave-error": "#ff0000",
"--editor-error-decoration": "underline wavy #ff0000",
// <value />: plain text, not the parent scheme's colour.
"--syn-property": "#a9b7c6",
});
expect(s.tokens["--syn-constant"]).toBeUndefined();
});
+25 -8
Переглянути файл
@@ -53,7 +53,8 @@ export const UI_KEYS: Record<string, string[]> = {
"--bg-checkbox": ["Checkbox.Background.Default"],
"--border-checkbox": ["Checkbox.Border.Default"],
"--bg-tab-selected": ["EditorTabs.underlinedTabBackground", "EditorTabs.selectedBackground"],
"--border-tab-selected": ["EditorTabs.underlineColor"],
// Islands paints the selected tab's border with `underlinedBorderColor`; the classic tabs underline with `underlineColor`.
"--border-tab-selected": ["EditorTabs.underlinedBorderColor", "EditorTabs.underlineColor"],
"--bg-tab-hover": ["EditorTabs.hoverBackground"],
"--border-editor-tabs": ["EditorTabs.borderColor"],
"--bg-toolbar-hover": ["ActionButton.hoverBackground"],
@@ -66,6 +67,7 @@ export const UI_KEYS: Record<string, string[]> = {
"--bg-counter": ["Counter.background"],
"--fg-counter": ["Counter.foreground"],
"--bg-search-match": ["SearchMatch.startBackground", "SearchMatch.endBackground"],
"--fg-completion-match": ["CompletionPopup.matchForeground"],
"--bg-toggle-on": ["ToggleButton.onBackground"],
"--fg-toggle-on": ["ToggleButton.onForeground"],
"--bg-toggle-off": ["ToggleButton.offBackground"],
@@ -89,7 +91,8 @@ export const ICLS_COLORS: Record<string, string[]> = {
SELECTION_BACKGROUND: ["--bg-editor-selection"],
LINE_NUMBERS_COLOR: ["--fg-line-number"],
LINE_NUMBER_ON_CARET_ROW_COLOR: ["--fg-line-number-current"],
GUTTER_BACKGROUND: ["--bg-editor-gutter"],
// The new UI paints the gutter with this, else with the editor's background; `GUTTER_BACKGROUND` is the classic UI's.
EDITOR_GUTTER_BACKGROUND: ["--bg-editor-gutter"],
INDENT_GUIDE: ["--border-indent-guide"],
SELECTED_INDENT_GUIDE: ["--border-indent-guide-active"],
RIGHT_MARGIN_COLOR: ["--border-right-margin"],
@@ -244,19 +247,33 @@ export function convertIcls(xml: string): { name: string; dark: boolean | null;
if (key && value !== undefined && ICLS_COLORS[key]) set(ICLS_COLORS[key], iclsColor(value));
}
const attributes = /<attributes>([\s\S]*?)<\/attributes>/.exec(xml)?.[1] ?? "";
for (const m of attributes.matchAll(/<option name="([^"]+)"[^>]*>\s*<value>([\s\S]*?)<\/value>\s*<\/option>/g)) {
const map = ICLS_ATTRIBUTES[m[1]!];
if (!map) continue;
const body = /<attributes>([\s\S]*?)<\/attributes>/.exec(xml)?.[1] ?? "";
const attributes = new Map<string, Map<string, string>>();
const empty = new Set<string>();
for (const m of body.matchAll(
/<option name="([^"]+)"[^>]*>\s*(?:<value\s*\/>|<value>([\s\S]*?)<\/value>)\s*<\/option>/g,
)) {
if (m[2] === undefined) empty.add(m[1]!);
const parts = new Map<string, string>();
for (const o of m[2]!.matchAll(/<option\b[^>]*\/>/g)) {
for (const o of (m[2] ?? "").matchAll(/<option\b[^>]*\/>/g)) {
const k = attr(o[0], "name");
const v = attr(o[0], "value");
if (k && v !== undefined) parts.set(k, v);
}
attributes.set(m[1]!, parts);
}
const textForeground = iclsColor(attributes.get("TEXT")?.get("FOREGROUND") ?? "");
for (const [key, parts] of attributes) {
const map = ICLS_ATTRIBUTES[key];
if (!map) continue;
// `<value />` defines the key without colours, so the parent scheme's don't apply: it shows as plain text.
if (empty.has(key)) {
for (const [part, token] of map) if (part === "FOREGROUND") set([token], textForeground);
continue;
}
for (const [part, token] of map) set([token], iclsColor(parts.get(part) ?? ""));
// FONT_TYPE: 1 bold, 2 italic, 3 bold italic.
if (m[1] === "DEFAULT_LINE_COMMENT" && parts.has("FONT_TYPE")) {
if (key === "DEFAULT_LINE_COMMENT" && parts.has("FONT_TYPE")) {
tokens["--syn-comment-style"] = Number(parts.get("FONT_TYPE")) & 2 ? "italic" : "normal";
}
}