дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
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
168 рядки
5.8 KiB
TypeScript
168 рядки
5.8 KiB
TypeScript
/**
|
|
* Source / test / resource / excluded roots of the open project: detected by
|
|
* the backend from build files (`project_roots`, refreshed on
|
|
* `projectRootsChanged`) and merged here with the Project Structure settings,
|
|
* so marking a folder shows at once.
|
|
*/
|
|
import { useMemo } from "react";
|
|
import { create } from "zustand";
|
|
import { call, commands, isTauri, onIdeEvent } from "./ipc";
|
|
import {
|
|
effectiveRoots,
|
|
isTestPath,
|
|
rootFor,
|
|
useProjectStructure,
|
|
validateProjectStructure,
|
|
type RootFolder,
|
|
} from "./languageSettings";
|
|
import { useProject } from "./project";
|
|
|
|
interface RootsState {
|
|
/** Project the roots belong to. */
|
|
root: string | null;
|
|
detected: RootFolder[];
|
|
}
|
|
|
|
export const useProjectRoots = create<RootsState>(() => ({ root: null, detected: [] }));
|
|
|
|
let generation = 0;
|
|
|
|
/** Re-reads the detected roots of the open project. */
|
|
export async function loadProjectRoots(): Promise<void> {
|
|
const root = useProject.getState().project?.root ?? null;
|
|
const gen = ++generation;
|
|
if (!root || !isTauri()) {
|
|
useProjectRoots.setState({ root, detected: [] });
|
|
return;
|
|
}
|
|
try {
|
|
const roots = await call(commands.projectRoots());
|
|
if (gen !== generation) return;
|
|
useProjectRoots.setState({
|
|
root,
|
|
detected: roots.detected.map((r) => ({ path: r.path, kind: r.kind, detectedFrom: r.detectedFrom ?? null })),
|
|
});
|
|
} catch {
|
|
if (gen === generation) useProjectRoots.setState({ root, detected: [] });
|
|
}
|
|
}
|
|
|
|
let users = 0;
|
|
let dispose: (() => void) | null = null;
|
|
|
|
/** Keeps the roots in sync with the open project (reference-counted; returns a disposer). */
|
|
export function initProjectRoots(): () => void {
|
|
if (users++ === 0) {
|
|
void loadProjectRoots();
|
|
const unsubProject = useProject.subscribe((s, prev) => {
|
|
if (s.project?.root !== prev.project?.root) void loadProjectRoots();
|
|
});
|
|
const unsubEvent = onIdeEvent("projectRootsChanged", () => void loadProjectRoots());
|
|
dispose = () => {
|
|
unsubProject();
|
|
unsubEvent();
|
|
};
|
|
}
|
|
let done = false;
|
|
return () => {
|
|
if (done) return;
|
|
done = true;
|
|
if (--users === 0) {
|
|
dispose?.();
|
|
dispose = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
function currentDetected(): RootFolder[] {
|
|
const s = useProjectRoots.getState();
|
|
return s.root === (useProject.getState().project?.root ?? null) ? s.detected : [];
|
|
}
|
|
|
|
/** Effective roots right now (non-reactive). */
|
|
export function currentRoots(): RootFolder[] {
|
|
return effectiveRoots(currentDetected(), validateProjectStructure(useProject.getState().settings?.projectStructure));
|
|
}
|
|
|
|
/** Detected roots of the open project right now (non-reactive). */
|
|
export function currentDetectedRoots(): RootFolder[] {
|
|
return currentDetected();
|
|
}
|
|
|
|
/** Effective roots (reactive). */
|
|
export function useEffectiveRoots(): RootFolder[] {
|
|
const structure = useProjectStructure();
|
|
const projectRoot = useProject((s) => s.project?.root ?? null);
|
|
const detected = useProjectRoots((s) => (s.root === projectRoot ? s.detected : null));
|
|
return useMemo(() => effectiveRoots(detected ?? [], structure), [detected, structure]);
|
|
}
|
|
|
|
/** Detected roots (reactive). */
|
|
export function useDetectedRoots(): RootFolder[] {
|
|
const projectRoot = useProject((s) => s.project?.root ?? null);
|
|
const detected = useProjectRoots((s) => (s.root === projectRoot ? s.detected : null));
|
|
return detected ?? EMPTY;
|
|
}
|
|
|
|
const EMPTY: RootFolder[] = [];
|
|
|
|
/** Project-relative path of an absolute `path`, or null outside the project. */
|
|
export function projectRelative(path: string): string | null {
|
|
const root = useProject.getState().project?.root;
|
|
if (!root) return null;
|
|
if (path === root) return "";
|
|
return path.startsWith(root + "/") ? path.slice(root.length + 1) : null;
|
|
}
|
|
|
|
/** The innermost root containing the absolute `path`. */
|
|
export function rootOfPath(path: string, roots: readonly RootFolder[] = currentRoots()): RootFolder | null {
|
|
const rel = projectRelative(path);
|
|
return rel === null ? null : rootFor(roots, rel);
|
|
}
|
|
|
|
/** Whether the absolute `path` is a test file (test root or test-file name). */
|
|
export function isTestFile(path: string, roots: readonly RootFolder[] = currentRoots()): boolean {
|
|
const rel = projectRelative(path);
|
|
return rel !== null && isTestPath(roots, rel);
|
|
}
|
|
|
|
/** IDEA's named scopes over the roots. */
|
|
export type ProjectFileSet = "all" | "production" | "test";
|
|
|
|
/** Whether the absolute `path` belongs to a named project scope ("Project Production Files" …). */
|
|
export function inFileSet(set: ProjectFileSet, path: string, roots: readonly RootFolder[] = currentRoots()): boolean {
|
|
const rel = projectRelative(path);
|
|
if (rel === null) return false;
|
|
const root = rootFor(roots, rel);
|
|
if (root?.kind === "excluded") return false;
|
|
if (set === "all") return true;
|
|
return isTestPath(roots, rel) === (set === "test");
|
|
}
|
|
|
|
/** A module: the project itself or a folder with a build file roots were detected from. */
|
|
export interface ProjectModule {
|
|
name: string;
|
|
/** Project-relative, `/`-separated; `""` is the project root. */
|
|
path: string;
|
|
}
|
|
|
|
/** Modules of the project, root first, then by path. */
|
|
export function projectModules(detected: readonly RootFolder[], projectName: string): ProjectModule[] {
|
|
const dirs = new Set([""]);
|
|
for (const r of detected) {
|
|
if (!r.detectedFrom) continue;
|
|
const i = r.detectedFrom.lastIndexOf("/");
|
|
dirs.add(i < 0 ? "" : r.detectedFrom.slice(0, i));
|
|
}
|
|
return [...dirs]
|
|
.sort((a, b) => (a === "" ? -1 : b === "" ? 1 : a < b ? -1 : a > b ? 1 : 0))
|
|
.map((path) => ({ path, name: path ? path.slice(path.lastIndexOf("/") + 1) : projectName }));
|
|
}
|
|
|
|
/** Modules of the open project (reactive). */
|
|
export function useProjectModules(): ProjectModule[] {
|
|
const detected = useDetectedRoots();
|
|
const name = useProject((s) => s.project?.name ?? "");
|
|
return useMemo(() => projectModules(detected, name), [detected, name]);
|
|
}
|