Make static symbol italics configurable (#70)

Python language servers mark class attributes `static`, so their declarations and `self.` references rendered italic, unlike PyCharm. Adds Color Scheme attributes for static symbols: Language Defaults › Identifiers › Static symbol (italic by default, as IDEA's static members) and Python › Static symbol (upright by default), applied to semantic tokens with the `static` modifier.

Verified in the app with basedpyright: a class attribute renders upright by default and italic when Python › Static symbol › Italic is on, applied live. Maintainer follow-up: formatted colorScheme.tsx.

https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
胡翀 authored and GitHub committed 2026-10-27 14:27:24 +08:00
1 джерело 12418e0184
коміт 566b4fdeb1
6 files changed
+41 -9

No files matched your search

+6
Переглянути файл
@@ -26,6 +26,12 @@ describe("semanticRanges", () => {
{ from: 34, to: 37, className: "cm-sem-constant" },
]);
});
it("applies the Python-specific static style without changing other languages", () => {
const input = { tokenTypes: ["property"], tokenModifiers: ["static"], data: [0, 0, 5, 0, 1] };
expect(semanticRanges(doc, input, false, "python")[0]?.className).toBe("cm-sem-property cm-sem-static cm-sem-python-static");
expect(semanticRanges(doc, input, false, "java")[0]?.className).toBe("cm-sem-property cm-sem-static");
});
});
describe("semantic highlighting (rainbow identifiers)", () => {
+3 -2
Переглянути файл
@@ -80,7 +80,7 @@ export interface SemanticRange {
}
/** Decodes absolute `[line, col, len, type, modifiers]` quintuples into styled ranges. */
export function semanticRanges(doc: Text, tokens: SemanticTokens, rainbow = false): SemanticRange[] {
export function semanticRanges(doc: Text, tokens: SemanticTokens, rainbow = false, language?: string): SemanticRange[] {
const out: SemanticRange[] = [];
const { data, tokenTypes, tokenModifiers } = tokens;
// Servers with a `local` modifier (TypeScript) tell module-level variables apart.
@@ -102,6 +102,7 @@ export function semanticRanges(doc: Text, tokens: SemanticTokens, rainbow = fals
if (constant && (type === "variable" || type === "property")) base = "constant";
if (base && base !== "variable") classes.push(`cm-sem-${base}`);
for (const n of names) if (MODIFIER_CLASS[n]) classes.push(`cm-sem-${MODIFIER_CLASS[n]}`);
if (language === "python" && names.includes("static")) classes.push("cm-sem-python-static");
const local =
rainbow &&
!constant &&
@@ -163,7 +164,7 @@ const semanticPlugin = ViewPlugin.fromClass(
}
decorate(doc: Text, tokens: SemanticTokens | null) {
this.last = { doc, tokens };
const ranges = tokens ? semanticRanges(doc, tokens, this.rainbow) : [];
const ranges = tokens ? semanticRanges(doc, tokens, this.rainbow, docOf(this.view)?.language) : [];
this.view.dispatch({
effects: setSemantic.of(Decoration.set(ranges.map((r) => markFor(r.className).range(r.from, r.to)), true)),
});
+2 -1
Переглянути файл
@@ -348,7 +348,8 @@ export const lspTheme = EditorView.theme({
".cm-sem-self": { color: "var(--syn-self, var(--syn-keyword))" },
".cm-sem-keyword": { color: "var(--syn-keyword)", fontWeight: "var(--syn-keyword-bold, inherit)" },
".cm-sem-builtin:not(.cm-sem-function)": { color: "var(--syn-builtin, inherit)" },
".cm-sem-static": { fontStyle: "italic" },
".cm-sem-static": { fontStyle: "var(--syn-static-italic, italic)" },
".cm-sem-python-static": { fontStyle: "var(--syn-python-static-italic, normal)" },
".cm-sem-mutable": { textDecoration: "underline", textDecorationColor: "var(--fg-faint)", textUnderlineOffset: "3px" },
".cm-sem-deprecated": { textDecoration: "line-through" },
// Semantic highlighting: after the base classes so a parameter's colour gives way, and over the
+21 -6
Переглянути файл
@@ -87,6 +87,8 @@ function previewVars(id: SchemeId, o: Record<string, TextAttributesOverride> | u
/** Text styled like an attribute in the preview. */
function tokenStyle(attr: AttributeDef | undefined): CSSProperties {
if (!attr?.token) return attr?.fg ? { color: `var(${attr.fg})` } : {};
if (attr.italicOnly)
return { fontStyle: `var(--syn-${attr.token}-italic, ${attr.defaultItalic === false ? "normal" : "italic"})` };
const t = attr.token;
return {
color: `var(--syn-${t})`,
@@ -111,6 +113,8 @@ const SAMPLE: Seg[][] = [
[" = ", "operator"],
["3", "number"],
],
[[" static_field", "staticSymbol"], [" = 3"]],
[[" python_field", "pyStaticSymbol"], [" = 3"]],
[
[" def ", "keyword"],
["greet", "functionDeclaration"],
@@ -150,7 +154,16 @@ function AttributeEditor({
};
return (
<div className={styles.attrEditor} aria-label={`${attr.name} attributes`}>
{attr.token && (
{attr.italicOnly && (
<div className={styles.attrRow}>
<Checkbox
checked={v.italic ?? attr.defaultItalic ?? true}
onChange={(b) => set({ italic: b === (attr.defaultItalic ?? true) ? undefined : b })}
label="Italic"
/>
</div>
)}
{attr.token && !attr.italicOnly && (
<>
<div className={styles.attrRow}>
<Checkbox checked={!!v.bold} onChange={(b) => set({ bold: b || undefined })} label="Bold" />
@@ -159,11 +172,11 @@ function AttributeEditor({
</>
)}
{attr.fg && <ColorRow label="Foreground" value={v.foreground} onChange={(c) => set({ foreground: c })} />}
{(attr.bg || attr.token) && (
{(attr.bg || (attr.token && !attr.italicOnly)) && (
<ColorRow label="Background" value={v.background} onChange={(c) => set({ background: c })} />
)}
{attr.effect && <ColorRow label="Effects" value={v.effectColor} onChange={(c) => set({ effectColor: c })} />}
{attr.token && (
{attr.token && !attr.italicOnly && (
<div className={styles.attrRow}>
<Checkbox
checked={!!v.effect && v.effect !== "none"}
@@ -193,9 +206,11 @@ function AttributeEditor({
)}
</div>
)}
<div className={styles.inherited}>
<T>Unchecked values are inherited from the scheme.</T>
</div>
{!attr.italicOnly && (
<div className={styles.inherited}>
<T>Unchecked values are inherited from the scheme.</T>
</div>
)}
</div>
);
}
+4
Переглянути файл
@@ -26,6 +26,10 @@ describe("color schemes", () => {
expect(over).toContain("--syn-keyword-bold: var(--fw-code-bold, 700) !important");
const regular = schemeCss("theme-default", { light: { keyword: { bold: false } } }, root);
expect(regular).toContain("--syn-keyword-bold: var(--fw-code, 400) !important");
const upright = schemeCss("theme-default", { light: { staticSymbol: { italic: false } } }, root);
expect(upright).toContain("--syn-static-italic: normal !important");
const pythonItalic = schemeCss("theme-default", { light: { pyStaticSymbol: { italic: true } } }, root);
expect(pythonItalic).toContain("--syn-python-static-italic: italic !important");
});
it("keeps editor weights out of the schemes", () => {
+5
Переглянути файл
@@ -29,6 +29,9 @@ export interface AttributeDef {
effect?: string;
/** Syntax token: bold / italic / background / effect come from `--syn-<token>-*`. */
token?: string;
/** A semantic modifier whose only configurable attribute is italic. */
italicOnly?: boolean;
defaultItalic?: boolean;
}
const syn = (id: string, name: string, path: string[], token: string): AttributeDef => ({
@@ -81,6 +84,7 @@ export const ATTRIBUTES: readonly AttributeDef[] = [
syn("parameter", "Identifiers › Parameter", [LD], "param"),
syn("constant", "Identifiers › Constant", [LD], "constant"),
syn("field", "Identifiers › Instance field", [LD], "property"),
{ id: "staticSymbol", name: "Identifiers › Static symbol", path: [LD], token: "static", italicOnly: true },
syn("functionDeclaration", "Identifiers › Function declaration", [LD], "function"),
syn("functionCall", "Identifiers › Function call", [LD], "function-call"),
syn("predefined", "Identifiers › Predefined symbol", [LD], "builtin"),
@@ -98,6 +102,7 @@ export const ATTRIBUTES: readonly AttributeDef[] = [
...[1, 2, 3].map((n) => syn(`bracket${n}`, `Rainbow brackets › Level ${n}`, [LD], `bracket-${n}`)),
syn("pySelf", "Self parameter", ["Python"], "self"),
{ id: "pyStaticSymbol", name: "Static symbol", path: ["Python"], token: "python-static", italicOnly: true, defaultItalic: false },
syn("pyKeywordArg", "Keyword argument", ["Python"], "keyword-arg"),
syn("pyDocstring", "Docstring", ["Python"], "docstring"),
syn("pyBuiltin", "Built-in name", ["Python"], "predefined"),