From 2a6cc686eb89d9c4d00cd8083f47e3614c2208e0 Mon Sep 17 00:00:00 2001 From: kevinh <45650311+kevinh458@users.noreply.github.com> Date: Mon, 2 Nov 2026 20:13:31 +0800 Subject: [PATCH] feat(project-view): show Maven and Gradle module folders in bold (#835) Module folders below the project root are shown in bold in the Project view, from the same build presentation the module icons use; the root and plain folders keep their style. Fixes #813 Co-authored-by: kevinh458 <45650311+kevinh458@users.noreply.github.com> --- src/features/project-tree/ProjectView.module.css | 3 +++ src/features/project-tree/ProjectView.tsx | 11 ++++++++++- src/platform/projectPresentation.test.ts | 14 +++++++++++++- src/platform/projectPresentation.ts | 5 +++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/features/project-tree/ProjectView.module.css b/src/features/project-tree/ProjectView.module.css index c23ebaf46..d499db233 100644 --- a/src/features/project-tree/ProjectView.module.css +++ b/src/features/project-tree/ProjectView.module.css @@ -40,6 +40,9 @@ color: var(--fg-strong); font-weight: 600; } +.moduleLabel { + font-weight: 600; +} /* IDEA's "excluded" look: ignored / excluded entries are dimmed. */ .excluded { color: var(--vcs-ignored); diff --git a/src/features/project-tree/ProjectView.tsx b/src/features/project-tree/ProjectView.tsx index f4e140f8d..363912c43 100644 --- a/src/features/project-tree/ProjectView.tsx +++ b/src/features/project-tree/ProjectView.tsx @@ -48,7 +48,12 @@ import { badgeKind } from "@/ui/symbolKind"; import { contextMenuItems, menuGroupContext, newMenuItems } from "./contextMenu"; import { openNewPopup } from "./newPopupModel"; import { presentationIcon, useJvmPresentation } from "@/platform/filePresentation"; -import { moduleFolderIcon, useProjectPresentation, type BuildPresentation } from "@/platform/projectPresentation"; +import { + isModuleFolder, + moduleFolderIcon, + useProjectPresentation, + type BuildPresentation, +} from "@/platform/projectPresentation"; import { relativePath } from "@/platform/project"; import { folderKind } from "./folderKind"; import { useHomeDir } from "./home"; @@ -270,6 +275,8 @@ const Row = memo(function Row({ node.dir || node.placeholder || node.icon ? iconFor(node, roots, root, build, detected) : presentationIcon(node.path, file, roots, root, build.spring); + const moduleFolder = + node.dir && !node.placeholder && !!root && isModuleFolder(relativePath(root, node.path), node.root, build.modules); const isEditing = !!editing && (node.placeholder ? editing.kind === "new" : editing.kind === "rename" && editing.key === node.key); const faded = node.ignored || deco.faded; @@ -310,6 +317,8 @@ const Row = memo(function Row({ node.root && styles.rootName, // Only the project itself is bold (IDEA); contributed roots like Scratches are not. node.root && !node.icon && styles.rootLabel, + // Maven / Gradle module folders are bold too, as in IDEA. + moduleFolder && styles.moduleLabel, faded && !deco.color && styles.excluded, deco.error && styles.error, )} diff --git a/src/platform/projectPresentation.test.ts b/src/platform/projectPresentation.test.ts index 54f6524d9..68a3d2643 100644 --- a/src/platform/projectPresentation.test.ts +++ b/src/platform/projectPresentation.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { buildPresentation, moduleFolderIcon } from "./projectPresentation"; +import { buildPresentation, isModuleFolder, moduleFolderIcon } from "./projectPresentation"; import type { BuildModel } from "./ipc"; const model = (partial: unknown) => ({ maven: null, gradle: null, ...(partial as object) }) as BuildModel; @@ -13,6 +13,18 @@ describe("build presentation context", () => { expect(moduleFolderIcon("app", false, modules)).toBe(`${tool}-module`); expect(moduleFolderIcon("plain", false, modules)).toBeNull(); }); + it("marks nested module folders, not the project root or plain folders", () => { + const modules = new Map([ + ["", "maven"], + ["core", "maven"], + ["services/api", "gradle"], + ]); + expect(isModuleFolder("core", false, modules)).toBe(true); + expect(isModuleFolder("services/api", false, modules)).toBe(true); + expect(isModuleFolder("", true, modules)).toBe(false); + expect(isModuleFolder("services", false, modules)).toBe(false); + expect(isModuleFolder("core/src", false, modules)).toBe(false); + }); it("includes Maven aggregators and modules without source roots", () => { const context = buildPresentation( model({ diff --git a/src/platform/projectPresentation.ts b/src/platform/projectPresentation.ts index 9e0648159..cc6f4f103 100644 --- a/src/platform/projectPresentation.ts +++ b/src/platform/projectPresentation.ts @@ -24,6 +24,11 @@ export function buildPresentation(model: BuildModel): BuildPresentation { return { modules, spring }; } +/** A Maven / Gradle module folder below the project root: IDEA shows its name in bold. */ +export function isModuleFolder(rel: string, root: boolean, modules: BuildPresentation["modules"]): boolean { + return !root && rel !== "" && modules.has(rel); +} + export function moduleFolderIcon(rel: string, root: boolean, modules: BuildPresentation["modules"]): string | null { const tool = modules.get(rel); return tool ? `${tool}-${root ? "project" : "module"}` : null;