mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-13 04:34:21 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d7b0835a5 | ||
|
|
b8c63327fe |
No files matched your search
@@ -5,6 +5,7 @@ import { setContext } from "@/platform/context";
|
||||
import type { BuildModel, GradleModel, ProjectInfo, RunConfig } from "@/platform/ipc";
|
||||
import { emitLocalIdeEvent } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { usePlatform } from "@/platform/platformInfo";
|
||||
import { GradleView, MavenView } from "./BuildToolView";
|
||||
import { activate } from "./index";
|
||||
import { expandableKeys, gradleChildren, gradleRoots, gradleProjectKey } from "./model";
|
||||
@@ -20,6 +21,7 @@ const mocks = vi.hoisted(() => ({
|
||||
confirm: vi.fn(),
|
||||
menu: vi.fn(),
|
||||
link: vi.fn(),
|
||||
notify: vi.fn(),
|
||||
unlink: vi.fn(),
|
||||
show: vi.fn(),
|
||||
sync: vi.fn(),
|
||||
@@ -52,6 +54,7 @@ vi.mock("@tauri-apps/api/core", async (original) => ({
|
||||
vi.mock("@/workbench/contributions", async (original) => ({
|
||||
...(await original<typeof import("@/workbench/contributions")>()),
|
||||
toolWindowService: () => ({ show: mocks.show }),
|
||||
notifications: () => ({ notify: mocks.notify }),
|
||||
}));
|
||||
vi.mock("@tauri-apps/plugin-dialog", () => ({ open: mocks.open }));
|
||||
vi.mock("@/workbench/messages", () => ({ confirm: mocks.confirm }));
|
||||
@@ -243,6 +246,43 @@ it.each([null, "/outside/pom.xml", "/p/a/build.xml", "/p/../outside/pom.xml", "/
|
||||
},
|
||||
);
|
||||
|
||||
it("links `\\\\?\\`-prefixed and differently-cased picks on Windows", async () => {
|
||||
const os = usePlatform.getState().os;
|
||||
usePlatform.setState({ os: "windows" });
|
||||
try {
|
||||
useProject.setState({ project: { root: "C:\\Projects\\demo" } as ProjectInfo });
|
||||
mocks.get.mockResolvedValue({
|
||||
languages: { java: { maven: { unlinkedProjects: [], linkedProjects: [] } } },
|
||||
});
|
||||
mocks.open.mockResolvedValue(["\\\\?\\C:\\Projects\\demo\\svc\\pom.xml", "c:/Projects/demo/web/pom.xml"]);
|
||||
await act(async () => {
|
||||
await executeAction("maven.addProjects");
|
||||
});
|
||||
expect(mocks.set).toHaveBeenCalledWith(
|
||||
"project",
|
||||
"languages.java.maven.linkedProjects",
|
||||
'["svc/pom.xml","web/pom.xml"]',
|
||||
);
|
||||
} finally {
|
||||
usePlatform.setState({ os });
|
||||
}
|
||||
});
|
||||
|
||||
it("warns about picks outside the project instead of silently doing nothing", async () => {
|
||||
mocks.open.mockResolvedValue("/other/pom.xml");
|
||||
await act(async () => {
|
||||
await executeAction("maven.addProjects");
|
||||
});
|
||||
expect(mocks.set).not.toHaveBeenCalled();
|
||||
expect(mocks.notify).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
level: "warning",
|
||||
title: expect.stringContaining("not added"),
|
||||
body: expect.stringContaining("/other/pom.xml"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("links picked POMs that no reactor lists as independent top-level projects", async () => {
|
||||
mocks.get.mockResolvedValue({
|
||||
languages: { java: { maven: { unlinkedProjects: ["b/pom.xml"], linkedProjects: ["tools/pom.xml"] } } },
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { buildToolRecovery } from "@/platform/buildToolRecovery";
|
||||
import { useBuildModel } from "@/platform/buildModel";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { hostOs } from "@/platform/remote";
|
||||
import { editorService, notifications } from "@/workbench/contributions";
|
||||
import { cancelTask, removeTask, upsertTask, useTasks } from "@/workbench/tasks";
|
||||
import {
|
||||
@@ -294,16 +295,40 @@ export async function unlinkProjects(pom: string | null) {
|
||||
await setLinked(root, [pom], false);
|
||||
}
|
||||
|
||||
/** Project-relative `pom.xml` paths of `picked` inside `root`; others are skipped. */
|
||||
function projectPoms(root: string, picked: readonly string[]): string[] {
|
||||
const base = root.replace(/\\/g, "/").replace(/\/$/, "");
|
||||
return picked.flatMap((p) => {
|
||||
const path = p.replace(/\\/g, "/");
|
||||
if (!path.startsWith(`${base}/`)) return [];
|
||||
/** A picked path in one comparable form: slashes unified, `\\?\` prefixes stripped. */
|
||||
function comparablePath(p: string) {
|
||||
const slashes = p.replace(/\\/g, "/");
|
||||
const unc = /^\/\/\?\/UNC\//i.exec(slashes);
|
||||
if (unc) return `//${slashes.slice(unc[0].length)}`;
|
||||
return slashes.replace(/^\/\/\?\//, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits `picked` into project-relative `pom.xml` paths inside `root` and the rest
|
||||
* (outside the project, not a `pom.xml`, or escaping via `.`/`..`). Drive-letter
|
||||
* case and `\\?\` prefixes must not matter on Windows, where the dialog may hand
|
||||
* back either form (#2626).
|
||||
*/
|
||||
function projectPoms(root: string, picked: readonly string[]): { inside: string[]; outside: string[] } {
|
||||
const base = comparablePath(root).replace(/\/$/, "");
|
||||
const ignoreCase = hostOs() === "windows";
|
||||
const inside: string[] = [];
|
||||
const outside: string[] = [];
|
||||
for (const p of picked) {
|
||||
const path = comparablePath(p);
|
||||
const underRoot = ignoreCase
|
||||
? path.slice(0, base.length + 1).toLowerCase() === `${base.toLowerCase()}/`
|
||||
: path.startsWith(`${base}/`);
|
||||
if (!underRoot) {
|
||||
outside.push(p);
|
||||
continue;
|
||||
}
|
||||
const pom = path.slice(base.length + 1);
|
||||
const parts = pom.split("/");
|
||||
return parts.some((part) => part === ".." || part === ".") || parts.pop() !== "pom.xml" ? [] : [pom];
|
||||
});
|
||||
if (parts.some((part) => part === ".." || part === ".") || parts.pop() !== "pom.xml") outside.push(p);
|
||||
else inside.push(pom);
|
||||
}
|
||||
return { inside, outside };
|
||||
}
|
||||
|
||||
export async function addProjects() {
|
||||
@@ -317,8 +342,19 @@ export async function addProjects() {
|
||||
defaultPath: root,
|
||||
filters: [{ name: "pom.xml", extensions: ["xml"] }],
|
||||
});
|
||||
const poms = projectPoms(root, typeof picked === "string" ? [picked] : (picked ?? []));
|
||||
const paths = typeof picked === "string" ? [picked] : (picked ?? []);
|
||||
if (!paths.length) return;
|
||||
const { inside: poms, outside } = projectPoms(root, paths);
|
||||
if (poms.length) await setLinked(root, poms, true);
|
||||
// Dropping every pick silently looks exactly like the button doing nothing (#2626).
|
||||
if (outside.length)
|
||||
notifications().notify({
|
||||
group: "tools",
|
||||
level: "warning",
|
||||
title: tx`Some Maven projects were not added`,
|
||||
body: tx`These are outside the open project, and Maven only links POMs inside it: ${outside.slice(0, 3).join(", ")}${outside.length > 3 ? ` (+${outside.length - 3} more)` : ""}`,
|
||||
key: "maven-add-outside-root",
|
||||
});
|
||||
}
|
||||
|
||||
/** Links independent Gradle builds without changing their files or build scripts. */
|
||||
|
||||
@@ -343,6 +343,8 @@
|
||||
"Editor and Preview": "编辑器和预览",
|
||||
"Preview Only": "仅预览",
|
||||
"Add Maven Projects": "添加 Maven 项目",
|
||||
"Some Maven projects were not added": "部分 Maven 项目未能添加",
|
||||
"These are outside the open project, and Maven only links POMs inside it: {join}{more}": "这些项目位于已打开项目之外,而 Maven 仅链接项目内的 POM:{join}{more}",
|
||||
"Unlink Maven Projects": "取消链接 Maven 项目",
|
||||
"Unlink Maven projects?": "取消链接 Maven 项目?",
|
||||
"Remove this project and its modules from Maven? Files will remain in the Project view.": "从 Maven 中移除此项目及其模块?文件将保留在项目视图中。",
|
||||
|
||||
Reference in new issue
Block a user