mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
Two folders can now be compared from the project view (Compare Directories with two folders selected, or Compare With… on one folder, also in Find Action). The result tree marks entries as only on left, only on right, contents differ or identical, with a name filter and a show-identical toggle; double-click opens the existing diff editor, and files or whole subdirectories can be copied to either side (overwrite and delete ask first, extra files in the target are kept). Comparison runs in the background with progress and cancel, honours the project's exclusions, and works for remote projects through the same file commands. The comparison tree and diff preview are shared with the Git folder/revision comparison. Verified on macOS (subdirectories, Chinese names, binary files, copy both ways, delete, cancel on large files); Windows and Linux not run. Fixes #520
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
/** Directory tree of changed files (IDEA's "Group by Directory"), single-child directory chains merged. */
|
|
|
|
export interface FileTreeNode<F> {
|
|
key: string;
|
|
/** Directory label (`src/app`) or file name. */
|
|
name: string;
|
|
file: F | null;
|
|
/** An explicit directory entry, including empty directories. */
|
|
directory?: F;
|
|
children: FileTreeNode<F>[];
|
|
/** Number of files below (1 for a file). */
|
|
count: number;
|
|
}
|
|
|
|
export function buildFileTree<F>(
|
|
files: readonly F[],
|
|
relPath: (f: F) => string,
|
|
isDirectory?: (f: F) => boolean,
|
|
): FileTreeNode<F>[] {
|
|
interface Dir {
|
|
dirs: Map<string, Dir>;
|
|
files: { name: string; file: F; rel: string }[];
|
|
entry?: F;
|
|
}
|
|
const root: Dir = { dirs: new Map(), files: [] };
|
|
for (const f of files) {
|
|
const rel = relPath(f);
|
|
const parts = rel.split("/").filter(Boolean);
|
|
const name = parts.pop() ?? rel;
|
|
let d = root;
|
|
for (const p of parts) {
|
|
let next = d.dirs.get(p);
|
|
if (!next) d.dirs.set(p, (next = { dirs: new Map(), files: [] }));
|
|
d = next;
|
|
}
|
|
if (isDirectory?.(f)) {
|
|
let sub = d.dirs.get(name);
|
|
if (!sub) d.dirs.set(name, (sub = { dirs: new Map(), files: [] }));
|
|
sub.entry = f;
|
|
} else d.files.push({ name, file: f, rel });
|
|
}
|
|
|
|
const convert = (d: Dir, prefix: string): FileTreeNode<F>[] => {
|
|
const out: FileTreeNode<F>[] = [];
|
|
for (const [name, sub] of [...d.dirs].sort(([a], [b]) => a.localeCompare(b))) {
|
|
let label = name;
|
|
let path = prefix ? `${prefix}/${name}` : name;
|
|
let cur = sub;
|
|
while (!cur.entry && cur.files.length === 0 && cur.dirs.size === 1) {
|
|
const [n, only] = [...cur.dirs][0]!;
|
|
label += `/${n}`;
|
|
path += `/${n}`;
|
|
cur = only;
|
|
}
|
|
const children = convert(cur, path);
|
|
out.push({
|
|
key: `d:${path}`,
|
|
name: label,
|
|
file: null,
|
|
directory: cur.entry,
|
|
children,
|
|
count: children.reduce((n, c) => n + c.count, 0),
|
|
});
|
|
}
|
|
for (const f of [...d.files].sort((a, b) => a.name.localeCompare(b.name)))
|
|
out.push({ key: `f:${f.rel}`, name: f.name, file: f.file, children: [], count: 1 });
|
|
return out;
|
|
};
|
|
return convert(root, "");
|
|
}
|
|
|
|
/** Keys of every directory node (for "expand all"). */
|
|
export function dirKeys<F>(nodes: readonly FileTreeNode<F>[], out: string[] = []): string[] {
|
|
for (const n of nodes)
|
|
if (!n.file) {
|
|
out.push(n.key);
|
|
dirKeys(n.children, out);
|
|
}
|
|
return out;
|
|
}
|