mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 00:07:50 +00:00
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:
co-authored by
muutot
Claude Opus 5.5
parent
4ca44ee815
commit
875e049dcd
@@ -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` |
|
||||
|
||||
@@ -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!,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
]);
|
||||
|
||||
|
||||
@@ -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))",
|
||||
|
||||
@@ -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)", () => {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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>' +
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"Predefined name usage": "预定义名称引用",
|
||||
"Predefined function definition": "预定义函数定义",
|
||||
"Byte string": "字节字符串",
|
||||
"F-string": "F 字符串",
|
||||
"Nested function definition": "嵌套函数定义",
|
||||
"Local variable": "局部变量",
|
||||
"Global variable": "全局变量",
|
||||
|
||||
+24
-4
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user