Файли
openidea/src/workbench/projectColor.ts
T
l0ng-ai 046fd37064 fix(ui): Match PyCharm project view, matched tags and text weight
- Project widget initials follow IDEA's AvatarUtils.initials ("idea" → "I").
- "Show directories with changed descendants" is off by default, as in IDEA.
- FileEntry gains `excluded`: only excluded folders get the excluded icon and
  dimming; git-ignored ones keep the plain icon and the ignored colour.
- `.github` folders get IDEA's GitHub folder icon.
- Matched tag names are never bold and use their own background
  (MATCHED_TAG_NAME); matched braces are bold in dark schemes only.
- Revert Greyscale antialiasing to `antialiased` for UI and editor; `auto`
  rendered text heavier than PyCharm.

Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
2026-10-24 22:56:48 +08:00

66 рядки
2.4 KiB
TypeScript
Неформатований Анотація Історія

Цей файл містить неоднозначні символи Юнікоду
Цей файл містить символи Юнікоду, які можна сплутати з іншими символами. Якщо ви вважаєте, що це зроблено навмисно, можете сміливо ігнорувати це попередження. Щоб показати їх, скористайтеся кнопкою Escape.
/**
* IDEA-style project identity: a colour slot (1–9, like RecentProject.ColorN)
* derived from the project name, and the initials shown in the
* title bar's project widget.
*/
export const PROJECT_COLOR_COUNT = 9;
/** Stable 1-based colour slot for a project name. */
export function projectColorIndex(name: string): number {
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) | 0;
return (Math.abs(h) % PROJECT_COLOR_COUNT) + 1;
}
/**
* IDEA's AvatarUtils.initials: two camel-case capitals, else the first letters
* of the first and last words, else one letter. "py-demo" → "PD",
* "MyProject" → "MP", "idea" → "I".
*/
export function projectInitials(name: string): string {
const text = name.trim();
const camel = camelInitials(text);
if (camel.length === 2) return camel;
for (const separators of [" ", ",", "-", "_", ".", "`'\""]) {
const words = text.split(new RegExp(`[${separators}]`)).filter(Boolean);
if (words.length >= 2) return ([...words[0]!][0]! + [...words.at(-1)!][0]!).toUpperCase();
}
return camel || "?";
}
/** First letter plus the next capital of the leading word: "MyProject" → "MP", "idea" → "I". */
function camelInitials(text: string): string {
const word = /\p{L}[\p{L}\p{N}]*/u.exec(text)?.[0] ?? "";
const [first, ...rest] = [...word];
if (!first) return "";
const capital = rest.find((c) => c !== c.toLowerCase());
return (first + (capital ?? "")).toUpperCase();
}
/**
* CSS for slot `n`: `--project-color` (avatar / gradient start) and
* `--project-color-end`. Theme tokens `--project-color-N(-end)` win when the
* theme defines them; the fallbacks keep the hues distinct without them.
*/
const FALLBACK: Record<number, [string, string]> = {
1: ["var(--syn-number)", "var(--danger)"],
2: ["var(--warning)", "var(--syn-type)"],
3: ["var(--success)", "var(--vcs-added)"],
4: ["var(--info)", "var(--link)"],
5: ["var(--accent)", "var(--syn-annotation)"],
6: ["var(--danger)", "var(--syn-annotation)"],
7: ["var(--syn-annotation)", "var(--accent)"],
8: ["var(--success)", "var(--info)"],
9: ["var(--vcs-added)", "var(--success)"],
};
export function projectColorVars(name: string): Record<string, string> {
const n = projectColorIndex(name);
const [start, end] = FALLBACK[n]!;
return {
"--project-color": `var(--project-color-${n}, ${start})`,
"--project-color-end": `var(--project-color-${n}-end, ${end})`,
};
}