дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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
Цей коміт міститься в:
6 files changed
+41
-9
No files matched your search
@@ -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)", () => {
|
||||
|
||||
@@ -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)),
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in new issue
Заблокувати користувача