Compare commits

...
Author SHA1 Message Date
l0ng-ai 6f0a291810 fix(project-view): offer Git and Find Usages in workspace members outside the workspace folder
The Project view's context menu counted a node as part of the project only inside the window's root, which in a workspace is the workspace folder. Members that live outside it ("../../repos/a") lost the Git submenu and Find Usages, although their repositories are discovered and their changes listed. Those entries now accept nodes inside the root or any workspace member; Mark Directory as stays limited to the root, whose project structure it edits, and Scratches stay outside.

Fixes #3111
2026-11-13 18:50:18 +08:00
2 changed files with 31 additions and 2 deletions

No files matched your search

@@ -301,6 +301,32 @@ it("offers workspace removal for every member root", () => {
);
});
it("offers Git and Find Usages in workspace members outside the workspace folder", () => {
useProject.setState({
project: {
root: "/work/workspaces/demo",
name: "demo",
settings: {},
trusted: true,
isGit: false,
workspace: { version: 1, name: "demo", projects: ["/work/repos/a", "/work/repos/b"] },
},
});
setContext("vcsAvailable", true);
try {
for (const target of [node("/work/repos/a", true, true), node("/work/repos/a/src", true)])
expect(sub(contextMenuItems([target]), "Git")).toContain("Commit…");
expect(sub(contextMenuItems([node("/work/repos/a/x.py")]), "Git")).toContain("Annotate with Git Blame");
expect(labels(contextMenuItems([node("/work/repos/a/x.py")]))).toContain("Find Usages");
// Project structure is the workspace folder's: its roots can't name a folder outside it.
expect(labels(contextMenuItems([node("/work/repos/a/src", true)]))).not.toContain("Mark Directory as");
// Scratches stay outside.
expect(sub(contextMenuItems([node("/config/scratches/s.py")]), "Git")).toBeUndefined();
} finally {
clearContext("vcsAvailable");
}
});
it("does not offer removal on the attached primary", () => {
useProject.setState({
project: {
+5 -2
View File
@@ -151,6 +151,9 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
const project = useProject.getState().project;
// Scratches and other contributed roots live outside the project: no VCS, roots or LSP usages.
const inProject = !!project && nodes.every((n) => isSameOrInside(n.path, project.root));
// Workspace members may live outside the workspace folder; they have VCS and usages too.
const memberRoots = project ? [project.root, ...(project.workspace?.projects ?? [])] : [];
const inWorkspace = nodes.every((n) => memberRoots.some((root) => isSameOrInside(n.path, root)));
const items: MenuItem[] = [];
const push = (...list: Entry[]) => list.forEach((i) => i && items.push(i));
@@ -228,7 +231,7 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
shortcut: keyLabel("project.open"),
run: () => files.forEach((f) => void openFile(f.path)),
},
inProject &&
inWorkspace &&
singleFile && {
id: "findUsages",
label: "Find Usages",
@@ -338,7 +341,7 @@ export function contextMenuItems(nodes: ProjectNode[]): MenuItem[] {
);
// Git › (`vcsAvailable` also covers repositories nested below a non-Git project root)
if (inProject && (project?.isGit || getContext().vcsAvailable === true)) {
if (inWorkspace && (project?.isGit || getContext().vcsAvailable === true)) {
const arg = vcsArg(nodes);
const dirs = nodes.some((n) => n.dir);
push(