fix(editor): align Python identifier colors and unused-parameter styling (#1572)

Python defaults diverged in several places: built-ins used the predefined magenta, __init__/__doc__ used function/self colours, instance fields the property colour, f-strings and byte strings the generic string colour, and the PEP 695 type keyword was uncoloured. Python now has its own tokens for built-ins, predefined definitions/usages, f-strings and byte strings; instance fields follow identifiers; type is a keyword. Unused parameters (not reported by Pyrefly) are greyed by a scope-aware local check that skips self/cls/_ names and signatures dictated elsewhere: stubs, @abstractmethod/@overload/@override, dunder methods and methods of classes with bases.

Co-authored-by: muutot <92625353+muutot@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Muu
2026-11-05 23:38:03 +08:00
committed by GitHub
co-authored by muutot Claude Opus 5.5
parent 4ca44ee815
commit 875e049dcd
14 changed files with 239 additions and 15 deletions
+7 -2
View File
@@ -324,7 +324,7 @@ a token.
<!-- BEGIN GENERATED TOKEN REFERENCE: pnpm gen:theme-docs -->
235 themeable tokens: 121 in the UI theme file, 114 in the editor colour scheme file.
240 themeable tokens: 121 in the UI theme file, 119 in the editor colour scheme file.
### Surfaces (UI theme)
@@ -436,11 +436,16 @@ a token.
| `--syn-constant` | `#c77dbb` | `#871094` |
| `--syn-python-local-variable` | `var(--syn-variable)` | `var(--syn-variable)` |
| `--syn-python-global-variable` | `var(--syn-global)` | `var(--syn-global)` |
| `--syn-python-instance-attribute` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-python-instance-attribute` | `var(--syn-variable)` | `var(--syn-variable)` |
| `--syn-python-class-attribute` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-python-constant` | `var(--syn-constant)` | `var(--syn-constant)` |
| `--syn-python-property` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-python-type-parameter` | `var(--syn-type)` | `var(--syn-type)` |
| `--syn-python-builtin` | `var(--syn-builtin)` | `var(--syn-builtin)` |
| `--syn-python-predefined-definition` | `var(--syn-predefined)` | `var(--syn-predefined)` |
| `--syn-python-predefined-usage` | `var(--syn-predefined)` | `var(--syn-predefined)` |
| `--syn-python-fstring` | `#a5c261` | `var(--syn-string)` |
| `--syn-python-bytes` | `#a5c261` | `var(--syn-string)` |
| `--syn-builtin` | `#8888c6` | `#000080` |
| `--syn-self` | `#94558d` | `#94558d` |
| `--syn-keyword-arg` | `#aa4926` | `#660099` |
+9
View File
@@ -26,6 +26,9 @@ const attributes = {
"PY.SELF_PARAMETER": { FOREGROUND: "aa7788" },
"PY.BUILTIN_NAME": { FOREGROUND: "bb8899" },
"PY.KEYWORD_ARGUMENT": { FOREGROUND: "cc99aa" },
"PY.STRING.F": { FOREGROUND: "aa8811" },
"PY.PREDEFINED_DEFINITION": { FOREGROUND: "bb9922" },
"PY.PREDEFINED_USAGE": { FOREGROUND: "ccaa33" },
OPENIDEA_pyLocalVariable: { FOREGROUND: "aa1101" },
OPENIDEA_pyGlobalVariable: { FOREGROUND: "aa2202" },
OPENIDEA_pyInstanceAttribute: { FOREGROUND: "aa3303" },
@@ -52,6 +55,8 @@ new EditorView({
parent: document.body,
state: EditorState.create({
doc: `MAX_SIZE: Final = 10
type Alias = list[int]
fstr = f"value={1}"
name = "global"
T = TypeVar("T")
@@ -69,9 +74,13 @@ class Foo[T]:
self.instance_attr = value
print(local, MAX_SIZE, name, T)
self.run(whatever=1) # Comment
print(self.__doc__)
@property
def size(self) -> int:
return self.instance_attr
def unused(param):
return 1
`,
extensions: [
support!,
+10 -2
View File
@@ -7,8 +7,11 @@ test("Python syntax supplements retain PyCharm colours without a language server
await expect(page.locator(".cm-content")).toBeVisible();
await page.screenshot({ path: info.outputPath("before.png") });
await page.goto("/e2e/pythonHighlight.html");
await expect(page.locator(".cm-sem-python-definition")).toHaveText("outer");
await expect(page.locator(".cm-sem-python-definition")).toHaveCSS("color", "rgb(34, 68, 102)");
await expect(page.locator(".cm-sem-python-definition").filter({ hasText: /^outer$/ })).toHaveText("outer");
await expect(page.locator(".cm-sem-python-definition").filter({ hasText: /^outer$/ })).toHaveCSS(
"color",
"rgb(34, 68, 102)",
);
await expect(page.locator(".cm-sem-python-nested")).toHaveCSS("color", "rgb(51, 85, 119)");
await expect(page.locator(".cm-sem-python-docstring")).toHaveCSS("color", "rgb(102, 136, 170)");
await expect(page.locator(".cm-sem-python-docstring")).toHaveCSS("text-decoration-line", "none");
@@ -46,6 +49,11 @@ test("Python syntax supplements retain PyCharm colours without a language server
await expect(page.locator(".cm-sem-python-constant").first()).toHaveCSS("color", "rgb(170, 85, 5)");
await expect(page.locator(".cm-sem-python-property").first()).toHaveCSS("color", "rgb(170, 102, 6)");
await expect(page.locator(".cm-sem-python-type-parameter").first()).toHaveCSS("color", "rgb(170, 119, 7)");
await expect(page.locator(".cm-sem-python-fstring").first()).toHaveCSS("color", "rgb(170, 136, 17)");
await expect(page.locator(".cm-sem-python-predefined-definition")).toHaveCSS("color", "rgb(187, 153, 34)");
await expect(page.locator(".cm-sem-python-predefined-usage")).toHaveCSS("color", "rgb(204, 170, 51)");
await expect(page.locator(".cm-sem-keyword").filter({ hasText: /^type$/ })).toHaveCSS("color", "rgb(17, 34, 51)");
await expect(page.locator(".cm-sem-unused")).toHaveCSS("color", "rgb(111, 115, 122)");
expect(errors).toEqual([]);
await page.screenshot({ path: info.outputPath("python-highlighting.png") });
});
@@ -24,11 +24,16 @@
"--syn-constant": "#e58fa3",
"--syn-python-local-variable": "#d9dfe8",
"--syn-python-global-variable": "#d9dfe8",
"--syn-python-instance-attribute": "#b9a3f0",
"--syn-python-instance-attribute": "#d9dfe8",
"--syn-python-class-attribute": "#b9a3f0",
"--syn-python-constant": "#e58fa3",
"--syn-python-property": "#b9a3f0",
"--syn-python-type-parameter": "#8fd4c9",
"--syn-python-builtin": "#6fc3df",
"--syn-python-predefined-definition": "#c4a0f2",
"--syn-python-predefined-usage": "#c4a0f2",
"--syn-python-fstring": "#9ccc7a",
"--syn-python-bytes": "#9ccc7a",
"--syn-builtin": "#6fc3df",
"--syn-self": "#d6849c",
"--syn-keyword-arg": "#c4a0f2",
+65
View File
@@ -50,6 +50,71 @@ class Foo:
);
});
it("marks unused parameters and highlights type and f-string keywords", async () => {
const state = await parse(`type Alias = list[int]
s = f"value={1}"
def used(x):
return x
def unused(param):
return 1
def skipped(self, _hidden):
return 1
`);
const tree = syntaxTree(state);
const kinds = pythonSymbolKinds(tree, state.doc);
const kindOf = (name: string) =>
[...kinds]
.filter(([range]) => {
const [from, to] = range.split(":").map(Number);
return state.doc.sliceString(from!, to!) === name;
})
.map(([, kind]) => kind);
expect(kindOf("param")).toEqual(["unusedParameter"]);
expect(kindOf("x")).toEqual(["parameter", "parameter"]);
expect(kindOf("self")).toEqual(["parameter"]);
expect(kindOf("_hidden")).toEqual(["parameter"]);
// Signatures dictated elsewhere: stubs, abstract / overloaded methods, dunders, overrides.
const stubs = await parse(`from abc import abstractmethod
class Plain:
def method(self, unused_a):
return 1
def stub(self, stub_a):
"""Doc."""
...
def todo(self, todo_a):
raise NotImplementedError
def nothing(self, pass_a): pass
@abstractmethod
def abstract(self, abstract_a):
return 1
def __exit__(self, exc_type, exc, tb):
return None
class Child(Plain):
def method(self, override_a):
return 2
`);
const stubKinds = pythonSymbolKinds(syntaxTree(stubs), stubs.doc);
const stubKindOf = (name: string) =>
[...stubKinds]
.filter(([range]) => {
const [from, to] = range.split(":").map(Number);
return stubs.doc.sliceString(from!, to!) === name;
})
.map(([, kind]) => kind);
expect(stubKindOf("unused_a")).toEqual(["unusedParameter"]);
for (const name of ["stub_a", "todo_a", "pass_a", "abstract_a", "exc_type", "tb", "override_a"])
expect(stubKindOf(name), name).toEqual(["parameter"]);
const ranges = pythonSyntaxRanges(tree, state.doc, kinds);
const styles = ranges.map((r) => [state.doc.sliceString(r.from, r.to), r.className]);
expect(styles).toContainEqual(["param", "cm-sem-parameter cm-sem-unused"]);
expect(styles).toContainEqual(["type", "cm-sem-keyword"]);
const fstring = ranges.filter((r) => r.className === "cm-sem-python-fstring");
expect(fstring.length).toBeGreaterThan(0);
expect(fstring.some((r) => state.doc.sliceString(r.from, r.to).includes("value="))).toBe(true);
});
it("lets server classification override the local builtin guess", async () => {
const state = await parse("print(1)");
const ranges = semanticRanges(
+89 -2
View File
@@ -19,6 +19,7 @@ export type PythonSymbolKind =
| "constant"
| "property"
| "typeParameter"
| "unusedParameter"
| "builtin";
const PYTHON_CLASSES: Record<PythonSymbolKind, string> = {
@@ -32,6 +33,7 @@ const PYTHON_CLASSES: Record<PythonSymbolKind, string> = {
constant: "cm-sem-constant cm-sem-python-constant",
property: "cm-sem-property cm-sem-python-property",
typeParameter: "cm-sem-typeParameter cm-sem-python-type-parameter",
unusedParameter: "cm-sem-parameter cm-sem-unused",
builtin: "cm-sem-builtin cm-sem-python-builtin",
};
@@ -91,6 +93,7 @@ const KIND_PRIORITY: Record<PythonSymbolKind, number> = {
globalVariable: 70,
typeParameter: 90,
parameter: 80,
unusedParameter: 80,
property: 80,
constant: 70,
classAttribute: 60,
@@ -106,6 +109,33 @@ function scope(node: SyntaxNode | null): SyntaxNode | null {
return null;
}
const STUB_STATEMENTS = new Set(["PassStatement", "RaiseStatement"]);
/**
* Whether the parameters `fn` doesn't read count as unused. Not for stubs (a body of only a
* docstring, `...`, `pass` or `raise`), `@abstractmethod` / `@overload` / `@override`, dunder
* methods or methods of a class with bases: those signatures are dictated elsewhere.
*/
function checksUnusedParameters(fn: SyntaxNode, doc: Text): boolean {
if (fn.name !== "FunctionDefinition") return true;
const name = directName(fn);
if (name && /^__\w+__$/.test(doc.sliceString(name.from, name.to))) return false;
if (fn.parent?.name === "DecoratedStatement")
for (let d = fn.parent.firstChild; d; d = d.nextSibling)
if (d.name === "Decorator" && /\b(?:abstractmethod|overload|override)\b/.test(doc.sliceString(d.from, d.to)))
return false;
const owner = scope(fn.parent);
if (owner?.name === "ClassDefinition" && owner.getChild("ArgList")) return false;
const body = fn.getChild("Body");
for (let st = body?.firstChild; st; st = st.nextSibling) {
if (st.name === ":" || st.name === "Comment" || STUB_STATEMENTS.has(st.name)) continue;
const only = st.name === "ExpressionStatement" ? st.firstChild : null;
if (only && !only.nextSibling && (only.name === "String" || only.name === "Ellipsis")) continue;
return true;
}
return false;
}
function enclosingClass(node: SyntaxNode | null): SyntaxNode | null {
for (; node; node = node.parent) if (node.name === "ClassDefinition") return node;
return null;
@@ -340,6 +370,24 @@ export function pythonSymbolKinds(tree: Tree, doc: Text): Map<string, PythonSymb
},
});
const usedParameters = new Set<string>();
tree.iterate({
enter(cursor) {
const node = cursor.node;
if (node.name !== "VariableName" || hasAncestor(node, "ParamList")) return;
const name = text(node);
let crossedFunction = false;
for (let current = scope(node); current; current = scope(current.parent)) {
if (FUNCTION_SCOPES.has(current.name)) crossedFunction = true;
const kind = bindings.get(key(current))?.get(name);
if (!kind) continue;
if (crossedFunction && current.name === "ClassDefinition" && kind !== "typeParameter") continue;
if (kind === "parameter") usedParameters.add(`${key(current)}:${name}`);
break;
}
},
});
const resolve = (
node: SyntaxNode,
name: string,
@@ -394,6 +442,18 @@ export function pythonSymbolKinds(tree: Tree, doc: Text): Map<string, PythonSymb
const kindFor = (node: SyntaxNode): PythonSymbolKind | undefined => {
const name = text(node);
if (node.name === "PropertyName") return memberKind(node, name);
if (node.parent?.name === "ParamList") {
const owner = scope(node);
if (
owner &&
name !== "self" &&
name !== "cls" &&
!name.startsWith("_") &&
!usedParameters.has(`${key(owner)}:${name}`) &&
checksUnusedParameters(owner, doc)
)
return "unusedParameter";
}
const start = node.parent?.name === "FunctionDefinition" ? scope(node.parent.parent) : scope(node);
const resolved = resolve(node, name, start);
if (resolved) return resolved;
@@ -423,8 +483,34 @@ export function pythonSyntaxRanges(
tree.iterate({
enter(cursor) {
const node = cursor.node;
if (!["VariableName", "PropertyName", "String", "Escape", "(", ")", "[", "]", "{", "}"].includes(node.name))
if (
![
"VariableName",
"PropertyName",
"String",
"FormatString",
"type",
"Escape",
"(",
")",
"[",
"]",
"{",
"}",
].includes(node.name)
)
return;
if (node.name === "FormatString") {
const className = "cm-sem-python-fstring";
let from = node.from;
for (let child = node.firstChild; child; child = child.nextSibling) {
if (child.name !== "FormatReplacement") continue;
if (child.from > from) ranges.push({ from, to: child.from, className, source: "pythonSyntax" });
from = child.to;
}
if (from < node.to) ranges.push({ from, to: node.to, className, source: "pythonSyntax" });
return;
}
const text = doc.sliceString(node.from, node.to);
const symbol = symbols.get(`${node.from}:${node.to}`);
let style: string | undefined;
@@ -433,7 +519,7 @@ export function pythonSyntaxRanges(
else if (["[", "]"].includes(node.name)) style = "python-brackets";
else if (["{", "}"].includes(node.name)) style = "python-braces";
else if (node.name === "VariableName") {
if (symbol === "typeParameter") className = pythonSymbolClasses(symbol);
if (symbol === "typeParameter" || symbol === "unusedParameter") className = pythonSymbolClasses(symbol);
else if (node.parent?.name === "FunctionDefinition") {
if (symbol === "property") className = pythonSymbolClasses(symbol);
else
@@ -467,6 +553,7 @@ export function pythonSyntaxRanges(
if (docstring && !/^[bf]/i.test(text)) style = "python-docstring";
else if (/^[rub]*b[rub]*['"]/i.test(text)) style = "python-bytes";
} else if (node.name === "Escape") style = "python-escape";
else if (node.name === "type" && node.parent?.name === "TypeDefinition") style = "keyword";
if (style) className = `cm-sem-${style}`;
if (className) ranges.push({ from: node.from, to: node.to, className, source: "pythonSyntax" });
if (style === "python-docstring") return false;
+2
View File
@@ -97,6 +97,7 @@ const PYTHON_VARIABLE_KINDS = new Set<PythonSymbolKind>([
"constant",
"property",
"typeParameter",
"unusedParameter",
]);
const PYTHON_SYMBOL_BASES = new Set([
"cm-sem-variable",
@@ -105,6 +106,7 @@ const PYTHON_SYMBOL_BASES = new Set([
"cm-sem-constant",
"cm-sem-type",
"cm-sem-typeParameter",
"cm-sem-parameter",
"cm-sem-builtin",
]);
+8 -3
View File
@@ -365,10 +365,14 @@ export const lspTheme = EditorView.theme({
".cm-sem-python-braces": pythonStyle("python-braces", "punct"),
".cm-sem-python-definition": pythonStyle("function", "function"),
".cm-sem-python-nested": pythonStyle("python-nested-function", "function"),
".cm-sem-python-predefined-definition": pythonStyle("python-predefined-definition", "function"),
".cm-sem-python-predefined-usage": pythonStyle("python-predefined-usage", "self"),
".cm-sem-python-predefined-definition": pythonStyle("python-predefined-definition", "predefined"),
".cm-sem-python-predefined-usage": pythonStyle("python-predefined-usage", "predefined"),
".cm-sem-python-docstring, .cm-sem-python-docstring *": pythonStyle("docstring", "string"),
".cm-sem-python-bytes": pythonStyle("python-bytes", "string"),
".cm-sem-python-fstring, .cm-sem-python-fstring .tok-string, .cm-sem-python-fstring .tok-string2": pythonStyle(
"python-fstring",
"string",
),
".cm-sem-python-escape": pythonStyle("escape", "escape"),
".cm-sem-python-format": pythonStyle("python-format", "escape"),
".cm-sem-python-keyword-arg": pythonStyle("keyword-arg", "param"),
@@ -385,12 +389,13 @@ export const lspTheme = EditorView.theme({
".cm-sem-variable": { color: "var(--syn-variable)" },
".cm-sem-property": { color: "var(--syn-property)" },
".cm-sem-parameter": { color: "var(--syn-param, var(--syn-variable))" },
".cm-sem-parameter.cm-sem-unused": { color: "var(--fg-unused, var(--fg-faint))" },
".cm-sem-self": { color: "var(--syn-self, var(--syn-keyword))" },
".cm-sem-keyword": { color: "var(--syn-keyword)", fontWeight: "var(--syn-keyword-bold, inherit)" },
// Before the builtin rule: library globals (`print`, `string`) keep the predefined colour.
".cm-sem-global:not(.cm-sem-function)": { color: "var(--syn-global, inherit)" },
".cm-sem-builtin:not(.cm-sem-function)": { color: "var(--syn-builtin, inherit)" },
".cm-sem-builtin.cm-sem-python-builtin": pythonStyle("predefined", "builtin"),
".cm-sem-builtin.cm-sem-python-builtin": pythonStyle("python-builtin", "builtin"),
".cm-sem-static": { fontStyle: "var(--syn-static-italic, italic)" },
".cm-sem-property.cm-sem-static, .cm-sem-property.cm-sem-static *": {
color: "var(--syn-static-property, var(--syn-property))",
+7
View File
@@ -12,6 +12,13 @@ describe("color schemes", () => {
expect(schemeTokens("light").get("--syn-keyword")).toBe("#0033b3");
expect(schemeTokens("dark").get("--syn-keyword")).toBe("#cf8e6d");
expect(schemeTokens("light").get("--bg-editor")).not.toMatch(/var\(/);
const dark = schemeTokens("dark");
expect(dark.get("--syn-python-builtin")).toBe("#8888c6");
expect(dark.get("--syn-python-predefined-definition")).toBe("#b200b2");
expect(dark.get("--syn-python-predefined-usage")).toBe("#b200b2");
expect(dark.get("--syn-python-instance-attribute")).toBe("#bcbec4");
expect(dark.get("--syn-python-fstring")).toBe("#a5c261");
expect(dark.get("--syn-python-bytes")).toBe("#a5c261");
});
it("gives PHP variables and function calls their own colours, as PhpStorm (#1532)", () => {
+2 -1
View File
@@ -142,6 +142,7 @@ const BASE_ATTRIBUTES: readonly AttributeDef[] = [
syn("pyBraces", "Braces", ["Python"], "python-braces"),
syn("pyNestedFunction", "Nested function definition", ["Python"], "python-nested-function"),
syn("pyBytes", "Byte string", ["Python"], "python-bytes"),
syn("pyFString", "F-string", ["Python"], "python-fstring"),
syn("pyPredefinedDefinition", "Predefined function definition", ["Python"], "python-predefined-definition"),
syn("pyPredefinedUsage", "Predefined name usage", ["Python"], "python-predefined-usage"),
syn("pyFormat", "Format specifier", ["Python"], "python-format"),
@@ -156,7 +157,7 @@ const BASE_ATTRIBUTES: readonly AttributeDef[] = [
},
syn("pyKeywordArg", "Keyword argument", ["Python"], "keyword-arg"),
syn("pyDocstring", "Docstring", ["Python"], "docstring"),
syn("pyBuiltin", "Built-in name", ["Python"], "predefined"),
syn("pyBuiltin", "Built-in name", ["Python"], "python-builtin"),
syn("pyDecorator", "Decorator", ["Python"], "annotation"),
syn("pyLocalVariable", "Local variable", ["Python"], "python-local-variable"),
syn("pyGlobalVariable", "Global variable", ["Python"], "python-global-variable"),
+8
View File
@@ -122,6 +122,14 @@ describe(".icls schemes", () => {
for (const [name, value] of names) expect(attrs[name]?.foreground).toBe(`#${value}`);
});
it("imports the Python f-string colour", () => {
const xml =
'<scheme name="F"><attributes><option name="PY.STRING.F"><value>' +
'<option name="FOREGROUND" value="808080"/></value></option></attributes></scheme>';
const { id, customs } = importScheme({}, parseIcls(xml).scheme);
expect(parseIcls(exportIcls(id, customs)).scheme.attributes.pyFString?.foreground).toBe("#808080");
});
it("imports the global variable colour", () => {
const xml =
'<scheme name="G" parent_scheme="Darcula"><attributes><option name="DEFAULT_GLOBAL_VARIABLE"><value>' +
+1
View File
@@ -149,6 +149,7 @@ Object.assign(ATTRIBUTE_NAMES, {
"PY.PREDEFINED_DEFINITION": "pyPredefinedDefinition",
"PY.PREDEFINED_USAGE": "pyPredefinedUsage",
"PY.STRING.B": "pyBytes",
"PY.STRING.F": "pyFString",
"PY.VALID_STRING_ESCAPE": "escape",
"PY.OPERATION_SIGN": "operator",
});
+1
View File
@@ -30,6 +30,7 @@
"Predefined name usage": "预定义名称引用",
"Predefined function definition": "预定义函数定义",
"Byte string": "字节字符串",
"F-string": "F 字符串",
"Nested function definition": "嵌套函数定义",
"Local variable": "局部变量",
"Global variable": "全局变量",
+24 -4
View File
@@ -127,11 +127,16 @@ html {
--syn-constant: #c77dbb;
--syn-python-local-variable: var(--syn-variable);
--syn-python-global-variable: var(--syn-global);
--syn-python-instance-attribute: var(--syn-property);
--syn-python-instance-attribute: var(--syn-variable);
--syn-python-class-attribute: var(--syn-property);
--syn-python-constant: var(--syn-constant);
--syn-python-property: var(--syn-property);
--syn-python-type-parameter: var(--syn-type);
--syn-python-builtin: var(--syn-builtin);
--syn-python-predefined-definition: var(--syn-predefined);
--syn-python-predefined-usage: var(--syn-predefined);
--syn-python-fstring: #a5c261;
--syn-python-bytes: #a5c261;
/* PhpStorm colours PHP variables and function calls on their own */
--syn-php-variable: var(--syn-property);
--syn-php-function-call: var(--syn-function);
@@ -521,11 +526,16 @@ html {
--syn-constant: #871094;
--syn-python-local-variable: var(--syn-variable);
--syn-python-global-variable: var(--syn-global);
--syn-python-instance-attribute: var(--syn-property);
--syn-python-instance-attribute: var(--syn-variable);
--syn-python-class-attribute: var(--syn-property);
--syn-python-constant: var(--syn-constant);
--syn-python-property: var(--syn-property);
--syn-python-type-parameter: var(--syn-type);
--syn-python-builtin: var(--syn-builtin);
--syn-python-predefined-definition: var(--syn-predefined);
--syn-python-predefined-usage: var(--syn-predefined);
--syn-python-fstring: var(--syn-string);
--syn-python-bytes: var(--syn-string);
--syn-php-variable: var(--syn-property);
--syn-php-function-call: var(--syn-function);
--syn-builtin: #000080;
@@ -789,11 +799,16 @@ html {
--syn-constant: #e0a0d6;
--syn-python-local-variable: var(--syn-variable);
--syn-python-global-variable: var(--syn-global);
--syn-python-instance-attribute: var(--syn-property);
--syn-python-instance-attribute: var(--syn-variable);
--syn-python-class-attribute: var(--syn-property);
--syn-python-constant: var(--syn-constant);
--syn-python-property: var(--syn-property);
--syn-python-type-parameter: var(--syn-type);
--syn-python-builtin: var(--syn-builtin);
--syn-python-predefined-definition: var(--syn-predefined);
--syn-python-predefined-usage: var(--syn-predefined);
--syn-python-fstring: var(--syn-string);
--syn-python-bytes: var(--syn-string);
--syn-php-variable: var(--syn-property);
--syn-php-function-call: var(--syn-function);
--syn-builtin: #b0b0f0;
@@ -1142,11 +1157,16 @@ html {
--syn-constant: var(--palette-number);
--syn-python-local-variable: var(--syn-variable);
--syn-python-global-variable: var(--syn-global);
--syn-python-instance-attribute: var(--syn-property);
--syn-python-instance-attribute: var(--syn-variable);
--syn-python-class-attribute: var(--syn-property);
--syn-python-constant: var(--syn-constant);
--syn-python-property: var(--syn-property);
--syn-python-type-parameter: var(--syn-type);
--syn-python-builtin: var(--syn-builtin);
--syn-python-predefined-definition: var(--syn-predefined);
--syn-python-predefined-usage: var(--syn-predefined);
--syn-python-fstring: var(--syn-string);
--syn-python-bytes: var(--syn-string);
--syn-php-variable: var(--syn-property);
--syn-php-function-call: var(--syn-function);
--syn-builtin: var(--palette-cyan);