дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
feat: Third pass aligning core experience with IntelliJ IDEA
Project & files - Project view modes (Project Files / Open Files / All Changed Files / Scratches); External Libraries for Python, JDK (src.zip), Node, Cargo, Go, Maven/Gradle jars; read-only jar/zip browsing - ⌘N New popup with type search and more file kinds; New Project, Rename Project, Reload All from Disk, Export/Import Settings, Power Save Mode - File type associations, extra content roots and exclude patterns (indexed and watched without reopening the project) Editor & navigation - Find bar: search scope (comments/strings), Preserve Case, Exclude, multiline, history; Find in Files: Module scope, custom pattern scopes, multiline - Search Everywhere filters, Assign Shortcut, settings results; named bookmark lists, file/folder bookmarks; Structure filters and Show Inherited; Recent Locations timestamps and breadcrumbs; Code Vision author - Completion options, editor tab placement/layout, inline completion (LSP 3.18) - Spell checking (Typo inspection, ide-spell) and Inspections settings with custom profiles; Code Style schemes, formatter markers, Do not format Run, VCS, services, database - Typed run configurations (Shell, Program, npm, Gradle, Maven, Docker, Compound), Modify options, Before launch, env dialog, Current File, Emulate terminal (PTY) - Git log graph/view options, Go to Hash/Branch/Tag, date filter keeps the graph; Pull/Merge/Rebase dialogs, Manage Remotes, Worktrees - Generic Services host (Docker connections/registries, run sessions); TODO scope/changes tabs with changelists; Coverage report import - Data sources: wire-compatible vendors, URL import, SSL certs, Options, Schemas and Advanced tabs, color and comment Workbench - Run Anything, Customize Main Toolbar, named tool window layouts, menu completeness, Code Cleanup, macros, "Don't Show Again", editor banners - Chinese translations for the new UI Claude-Session: https://claude.ai/code/session_01SaPPsVPuk6ZTNsoUCNCbMv
Цей коміт міститься в:
1 джерело
294198a727
коміт
1e00ea5ee2
426 files changed
+85241
-2886
No files matched your search
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* External Libraries: the project's SDK / library source folders (the Python
|
||||
* interpreter's standard library and site-packages, the JDK, node_modules
|
||||
* packages, Cargo crates and the Rust standard library, GOROOT and Go modules,
|
||||
* Maven / Gradle dependency jars) as a Project view root. Their files open
|
||||
* read-only, like IDEA's library sources. Archives (the JDK's `src.zip`, jars)
|
||||
* are browsed through the backend's `<archive>!/<entry>` paths.
|
||||
*/
|
||||
import { commands, type LibraryGroup } from "@/bindings";
|
||||
import { setLibraryRoots } from "@/editor/api";
|
||||
import { call, isTauri, onIdeEvent } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { registerProjectViewRoot, type ProjectViewRootContribution } from "@/workbench/contributions";
|
||||
|
||||
export const EXTERNAL_LIBRARIES_LABEL = "External Libraries";
|
||||
|
||||
const GROUP_ICONS: Record<string, string> = {
|
||||
python: "file-python",
|
||||
jdk: "file-java",
|
||||
nodeCore: "file-js",
|
||||
nodeModules: "file-npm",
|
||||
rust: "file-rust",
|
||||
cargo: "file-cargo",
|
||||
go: "file-go",
|
||||
goModules: "file-gomod",
|
||||
maven: "maven-project",
|
||||
gradle: "file-gradle",
|
||||
};
|
||||
|
||||
/** An archive's root (`/jdk/lib/src.zip!`): its entries live below `<archive>!/`. */
|
||||
const isArchiveRoot = (path: string) => path.endsWith("!");
|
||||
|
||||
/** Files whose change can change the libraries (the backend re-detects only the affected ecosystem). */
|
||||
const MANIFESTS = new Set([
|
||||
"pyvenv.cfg",
|
||||
"pom.xml",
|
||||
"build.gradle",
|
||||
"build.gradle.kts",
|
||||
"settings.gradle",
|
||||
"settings.gradle.kts",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"yarn.lock",
|
||||
"pnpm-lock.yaml",
|
||||
"bun.lockb",
|
||||
"Cargo.toml",
|
||||
"Cargo.lock",
|
||||
"rust-toolchain",
|
||||
"rust-toolchain.toml",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
"go.work",
|
||||
]);
|
||||
|
||||
/** Whether an `fsChanged` event touches a library manifest. */
|
||||
export function touchesManifest(paths: readonly string[]): boolean {
|
||||
return paths.some(
|
||||
(p) =>
|
||||
!/[\\/]node_modules[\\/]/.test(p) && MANIFESTS.has(p.slice(Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\")) + 1)),
|
||||
);
|
||||
}
|
||||
|
||||
/** The "External Libraries" node for `groups` (`< Python 3.12 (.venv) >` › `python3.12`, `site-packages`). */
|
||||
export function librariesRoot(groups: readonly LibraryGroup[]): ProjectViewRootContribution | null {
|
||||
if (!groups.length) return null;
|
||||
return {
|
||||
id: "externalLibraries",
|
||||
label: EXTERNAL_LIBRARIES_LABEL,
|
||||
icon: "library",
|
||||
// After the project, before Scratches and Consoles (IDEA's order).
|
||||
order: 5,
|
||||
readOnly: true,
|
||||
children: groups.map((g) => ({
|
||||
label: `< ${g.name} >`,
|
||||
icon: GROUP_ICONS[g.kind] ?? "library",
|
||||
children: g.roots.map((r) => ({
|
||||
label: r.name,
|
||||
icon: isArchiveRoot(r.path) ? "file-archive" : "folder-library",
|
||||
path: r.path,
|
||||
detail: "library root",
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/** Settings that change which SDKs the project uses. */
|
||||
const sdkKey = (s: ReturnType<typeof useProject.getState>) => {
|
||||
const l = s.settings?.languages;
|
||||
return JSON.stringify([
|
||||
s.project?.root ?? null,
|
||||
l?.python?.interpreter,
|
||||
l?.java?.jdk,
|
||||
l?.node?.interpreter,
|
||||
l?.rust?.toolchain,
|
||||
l?.rust?.stdlib,
|
||||
l?.go?.goroot,
|
||||
l?.go?.gopath,
|
||||
]);
|
||||
};
|
||||
|
||||
const MANIFEST_REFRESH_DELAY = 500;
|
||||
|
||||
export function activate(): () => void {
|
||||
let disposeRoot = () => {};
|
||||
let disposeReadOnly = () => {};
|
||||
let seq = 0;
|
||||
let shown = "";
|
||||
const clear = () => {
|
||||
disposeRoot();
|
||||
disposeReadOnly();
|
||||
disposeRoot = disposeReadOnly = () => {};
|
||||
shown = "";
|
||||
};
|
||||
const refresh = () => {
|
||||
const id = ++seq;
|
||||
if (!useProject.getState().project || !isTauri()) return clear();
|
||||
call(commands.sdkProjectLibraries())
|
||||
.then((groups) => {
|
||||
if (id !== seq) return;
|
||||
const key = JSON.stringify(groups);
|
||||
if (key === shown) return;
|
||||
clear();
|
||||
shown = key;
|
||||
const root = librariesRoot(groups);
|
||||
if (!root) return;
|
||||
disposeReadOnly = setLibraryRoots(
|
||||
"externalLibraries",
|
||||
groups.flatMap((g) => g.roots.map((r) => r.path)),
|
||||
);
|
||||
disposeRoot = registerProjectViewRoot(root);
|
||||
})
|
||||
.catch((e: unknown) => console.warn("external libraries: cannot list SDK roots", e));
|
||||
};
|
||||
refresh();
|
||||
const unsub = useProject.subscribe((s, prev) => {
|
||||
if (sdkKey(s) !== sdkKey(prev)) refresh();
|
||||
});
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const unsubFs = onIdeEvent("fsChanged", (e) => {
|
||||
if (!touchesManifest([...e.created, ...e.modified, ...e.removed])) return;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(refresh, MANIFEST_REFRESH_DELAY);
|
||||
});
|
||||
return () => {
|
||||
seq++;
|
||||
clearTimeout(timer);
|
||||
unsub();
|
||||
unsubFs();
|
||||
clear();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { isLibraryPath, setLibraryRoots } from "@/editor/api";
|
||||
import { librariesRoot, touchesManifest } from "./index";
|
||||
|
||||
describe("External Libraries", () => {
|
||||
let dispose = () => {};
|
||||
afterEach(() => dispose());
|
||||
|
||||
it("groups each SDK's folders under one read-only root", () => {
|
||||
expect(librariesRoot([])).toBeNull();
|
||||
const root = librariesRoot([
|
||||
{
|
||||
name: "Python 3.12 (.venv)",
|
||||
kind: "python",
|
||||
roots: [
|
||||
{ name: "python3.12", path: "/py/lib/python3.12" },
|
||||
{ name: "site-packages", path: "/p/.venv/lib/python3.12/site-packages" },
|
||||
],
|
||||
},
|
||||
])!;
|
||||
expect(root).toMatchObject({ id: "externalLibraries", label: "External Libraries", readOnly: true });
|
||||
expect(root.children?.[0]).toMatchObject({ label: "< Python 3.12 (.venv) >", icon: "file-python" });
|
||||
expect(root.children?.[0]?.children?.map((c) => [c.label, c.path, c.detail])).toEqual([
|
||||
["python3.12", "/py/lib/python3.12", "library root"],
|
||||
["site-packages", "/p/.venv/lib/python3.12/site-packages", "library root"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("gives each ecosystem's groups their icon", () => {
|
||||
const root = librariesRoot(
|
||||
["jdk", "nodeCore", "nodeModules", "rust", "cargo", "go", "goModules", "maven", "gradle", "other"].map((kind) => ({
|
||||
name: kind,
|
||||
kind,
|
||||
roots: [{ name: "r", path: `/libs/${kind}` }],
|
||||
})),
|
||||
)!;
|
||||
expect(root.children?.map((g) => g.icon)).toEqual([
|
||||
"file-java",
|
||||
"file-js",
|
||||
"file-npm",
|
||||
"file-rust",
|
||||
"file-cargo",
|
||||
"file-go",
|
||||
"file-gomod",
|
||||
"maven-project",
|
||||
"file-gradle",
|
||||
"library",
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists archives (src.zip, dependency jars) as browsable roots", () => {
|
||||
const root = librariesRoot([
|
||||
{
|
||||
name: "temurin-21",
|
||||
kind: "jdk",
|
||||
roots: [
|
||||
{ name: "temurin-21", path: "/jdk" },
|
||||
{ name: "src.zip", path: "/jdk/lib/src.zip!" },
|
||||
],
|
||||
},
|
||||
{ name: "Maven: g:a:1", kind: "maven", roots: [{ name: "a-1-sources.jar", path: "/m2/g/a/1/a-1-sources.jar!" }] },
|
||||
])!;
|
||||
expect(root.children?.[0]?.children?.map((c) => c.icon)).toEqual(["folder-library", "file-archive"]);
|
||||
expect(root.children?.[1]).toMatchObject({ label: "< Maven: g:a:1 >", icon: "maven-project" });
|
||||
dispose = setLibraryRoots("test", ["/jdk/lib/src.zip!"]);
|
||||
expect(isLibraryPath("/jdk/lib/src.zip!/java.base/java/lang/String.java")).toBe(true);
|
||||
});
|
||||
|
||||
it("refreshes on manifest changes outside node_modules", () => {
|
||||
expect(touchesManifest(["/p/src/main.rs", "/p/Cargo.toml"])).toBe(true);
|
||||
expect(touchesManifest(["C:\\p\\go.mod"])).toBe(true);
|
||||
expect(touchesManifest(["/p/.venv/pyvenv.cfg"])).toBe(true);
|
||||
expect(touchesManifest(["/p/node_modules/react/package.json"])).toBe(false);
|
||||
expect(touchesManifest(["/p/src/package.json.bak", "/p/README.md"])).toBe(false);
|
||||
});
|
||||
|
||||
it("marks files below library roots for read-only editors", () => {
|
||||
dispose = setLibraryRoots("test", ["/py/lib/python3.12"]);
|
||||
expect(isLibraryPath("/py/lib/python3.12/os.py")).toBe(true);
|
||||
expect(isLibraryPath("/py/lib/python3.120/x.py")).toBe(false);
|
||||
dispose();
|
||||
expect(isLibraryPath("/py/lib/python3.12/os.py")).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Заблокувати користувача