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
Цей коміт міститься в:
l0ng-ai committed 2026-10-25 21:24:00 +08:00
1 джерело 294198a727
коміт 1e00ea5ee2
426 files changed
+85241 -2886

No files matched your search

+151
Переглянути файл
@@ -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();
};
}
+84
Переглянути файл
@@ -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);
});
});