дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
- 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
66 рядки
2.4 KiB
TypeScript
66 рядки
2.4 KiB
TypeScript
/**
|
||
* 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})`,
|
||
};
|
||
}
|