дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
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
Цей коміт міститься в:
1 джерело
cacddee19d
коміт
046fd37064
26 files changed
+143
-36
No files matched your search
@@ -234,6 +234,8 @@ const Row = memo(function Row({
|
||||
const isEditing =
|
||||
!!editing && (node.placeholder ? editing.kind === "new" : editing.kind === "rename" && editing.key === node.key);
|
||||
const faded = node.ignored || deco.faded;
|
||||
// IDEA dims the icon of excluded folders only; git-ignored ones just get the ignored colour.
|
||||
const dimIcon = node.excluded;
|
||||
return (
|
||||
<span
|
||||
className={clsx(styles.content, cut && styles.cut, dropTarget && styles.dropTarget)}
|
||||
@@ -244,7 +246,7 @@ const Row = memo(function Row({
|
||||
onClick={(e) => onClick(node, e)}
|
||||
onPointerDown={(e) => onPointerDown(node, e)}
|
||||
>
|
||||
<Icon name={icon} size={16} className={clsx(styles.icon, node.dir && styles.folderIcon, faded && styles.faded)} />
|
||||
<Icon name={icon} size={16} className={clsx(styles.icon, node.dir && styles.folderIcon, dimIcon && styles.faded)} />
|
||||
{isEditing && editing ? (
|
||||
<InlineEditor
|
||||
editing={editing}
|
||||
|
||||
@@ -50,6 +50,7 @@ function nodesFrom(arg: unknown): ProjectNode[] {
|
||||
root: p === useProjectTree.getState().root,
|
||||
entry: null,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
symlink: false,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ const node = (path: string, dir = false, root = false): ProjectNode => ({
|
||||
root,
|
||||
entry: null,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
symlink: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ const entry = (path: string, kind: "file" | "dir" = "file"): FileEntry => ({
|
||||
size: 0,
|
||||
mtimeMs: 1,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
hidden: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Folder icon kind in the project view, from the effective roots. */
|
||||
import { rootRole, type FolderRole, type RootFolder } from "@/platform/languageSettings";
|
||||
import { relativePath } from "@/platform/project";
|
||||
import { basename, relativePath } from "@/platform/project";
|
||||
import type { FolderKind } from "@/ui";
|
||||
import type { ProjectNode } from "./model";
|
||||
|
||||
@@ -12,13 +12,17 @@ const ROLE_KIND: Record<FolderRole, FolderKind> = {
|
||||
excluded: "excluded",
|
||||
};
|
||||
|
||||
/** Project Structure marks and roots detected from build files; ignored entries look excluded. */
|
||||
/**
|
||||
* Project Structure marks and roots detected from build files. Like IDEA, only
|
||||
* excluded folders get the excluded icon; git-ignored ones keep the plain one.
|
||||
*/
|
||||
export function folderKind(
|
||||
node: Pick<ProjectNode, "path" | "ignored">,
|
||||
node: Pick<ProjectNode, "path" | "excluded">,
|
||||
roots: readonly RootFolder[],
|
||||
root: string | null,
|
||||
): FolderKind {
|
||||
if (node.ignored) return "excluded";
|
||||
if (node.excluded) return "excluded";
|
||||
const role = root ? rootRole(roots, relativePath(root, node.path)) : null;
|
||||
return role ? ROLE_KIND[role] : "plain";
|
||||
if (role) return ROLE_KIND[role];
|
||||
return basename(node.path) === ".github" ? "github" : "plain";
|
||||
}
|
||||
@@ -78,13 +78,15 @@ export class MemoryFs implements ProjectFs {
|
||||
.map((p) => {
|
||||
const c = this.nodes.get(p)!;
|
||||
const name = nameOf(p);
|
||||
const excluded = [...this.ignored].some((i) => p === i || isInside(p, i));
|
||||
return {
|
||||
name,
|
||||
path: p,
|
||||
kind: c.kind,
|
||||
size: c.kind === "file" ? c.size : 0,
|
||||
mtimeMs: 0,
|
||||
ignored: [...this.ignored].some((i) => p === i || isInside(p, i)),
|
||||
ignored: excluded,
|
||||
excluded,
|
||||
hidden: name.startsWith("."),
|
||||
} satisfies FileEntry;
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
|
||||
const entry = (path: string, kind: "file" | "dir" = "file", extra: Partial<FileEntry> = {}): FileEntry => {
|
||||
const name = path.slice(path.lastIndexOf("/") + 1);
|
||||
return { name, path, kind, size: 0, mtimeMs: 1, ignored: false, hidden: name.startsWith("."), ...extra };
|
||||
return { name, path, kind, size: 0, mtimeMs: 1, ignored: false, excluded: false, hidden: name.startsWith("."), ...extra };
|
||||
};
|
||||
|
||||
const ROOT = { path: "/r", name: "acme" };
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface ProjectNode {
|
||||
root: boolean;
|
||||
entry: FileEntry | null;
|
||||
ignored: boolean;
|
||||
/** Excluded by the exclude settings / Project Structure (not just git-ignored). */
|
||||
excluded: boolean;
|
||||
symlink: boolean;
|
||||
/** Placeholder row for the inline "new file / folder" editor. */
|
||||
placeholder?: "file" | "dir";
|
||||
@@ -120,6 +122,7 @@ export function buildModel(
|
||||
root: true,
|
||||
entry: null,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
symlink: false,
|
||||
};
|
||||
byKey.set(rootNode.key, rootNode);
|
||||
@@ -137,6 +140,7 @@ export function buildModel(
|
||||
root: false,
|
||||
entry,
|
||||
ignored: entry.ignored,
|
||||
excluded: entry.excluded,
|
||||
symlink: entry.kind === "symlink",
|
||||
};
|
||||
}
|
||||
@@ -151,7 +155,7 @@ export function buildModel(
|
||||
const visible = visibleEntries(listing, opts);
|
||||
if (visible.length !== 1) break;
|
||||
const only = visible[0]!;
|
||||
if (!isDirEntry(only) || only.ignored !== entry.ignored) break;
|
||||
if (!isDirEntry(only) || only.ignored !== entry.ignored || only.excluded !== entry.excluded) break;
|
||||
if (placeholder && placeholder.parent === only.path) break;
|
||||
end = only;
|
||||
chain.push(only.path);
|
||||
@@ -168,6 +172,7 @@ export function buildModel(
|
||||
root: false,
|
||||
entry: end,
|
||||
ignored: entry.ignored,
|
||||
excluded: entry.excluded,
|
||||
symlink: entry.kind === "symlink",
|
||||
};
|
||||
};
|
||||
@@ -186,6 +191,7 @@ export function buildModel(
|
||||
root: false,
|
||||
entry: null,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
symlink: false,
|
||||
placeholder: placeholder.kind,
|
||||
};
|
||||
@@ -312,6 +318,7 @@ const sameEntry = (a: FileEntry, b: FileEntry) =>
|
||||
a.size === b.size &&
|
||||
a.mtimeMs === b.mtimeMs &&
|
||||
a.ignored === b.ignored &&
|
||||
a.excluded === b.excluded &&
|
||||
a.hidden === b.hidden;
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ const node = (path: string, dir = false): ProjectNode => ({
|
||||
root: path === "/p",
|
||||
entry: null,
|
||||
ignored: false,
|
||||
excluded: false,
|
||||
symlink: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -26,13 +26,14 @@ const MAVEN: RootFolder[] = [
|
||||
|
||||
describe("folder icons", () => {
|
||||
it("follow detected and marked roots", () => {
|
||||
const kind = (rel: string, ignored = false) => folderKind({ path: `/p/${rel}`, ignored }, MAVEN, "/p");
|
||||
const kind = (rel: string, excluded = false) => folderKind({ path: `/p/${rel}`, excluded }, MAVEN, "/p");
|
||||
expect(kind("src/main/java")).toBe("source");
|
||||
expect(kind("src/test/java")).toBe("test");
|
||||
expect(kind("src/test/resources")).toBe("testResources");
|
||||
expect(kind("target")).toBe("excluded");
|
||||
expect(kind("src")).toBe("plain");
|
||||
expect(kind("docs", true)).toBe("excluded");
|
||||
expect(kind(".github")).toBe("github");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Заблокувати користувача