Repository navigation
Expand file tree
/
Copy pathdiffFileActions.ts
More file actions
112 lines (96 loc) · 3.78 KB
/
Copy pathdiffFileActions.ts
File metadata and controls
112 lines (96 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import type { ScopedThreadRef } from "@t3tools/contracts";
import {
isAbsolutePath,
isWindowsAbsolutePath,
normalizeProjectPathForComparison,
workspaceRelativeFilePath,
} from "@t3tools/shared/path";
import { useRightPanelStore } from "./rightPanelStore";
import { resolvePathLinkTarget } from "@t3tools/shared/fileLinks";
interface OpenDiffFilePrimaryActionInput {
readonly threadRef: ScopedThreadRef | null;
readonly filePath: string;
readonly activeCwd: string | undefined;
readonly repositoryRoot?: string | undefined;
readonly openInEditor: (targetPath: string) => void;
}
function normalizedRelativePathSegments(filePath: string): ReadonlyArray<string> | null {
if (filePath.startsWith("/") || isWindowsAbsolutePath(filePath) || /^[a-zA-Z]:/.test(filePath)) {
return null;
}
const segments = filePath
.replaceAll("\\", "/")
.split("/")
.filter((segment) => segment.length > 0 && segment !== ".");
if (segments.length === 0 || segments.includes("..")) return null;
return segments;
}
function repositoryRelativeWorkspaceSegments(
workspaceRoot: string | undefined,
repositoryRoot: string | undefined,
): ReadonlyArray<string> | null {
if (!workspaceRoot || !repositoryRoot) return null;
const normalizedWorkspaceRoot = normalizeProjectPathForComparison(workspaceRoot);
const normalizedRepositoryRoot = normalizeProjectPathForComparison(repositoryRoot);
if (normalizedWorkspaceRoot === normalizedRepositoryRoot) return [];
const separator = normalizedRepositoryRoot.includes("\\") ? "\\" : "/";
const repositoryPrefix = normalizedRepositoryRoot.endsWith(separator)
? normalizedRepositoryRoot
: `${normalizedRepositoryRoot}${separator}`;
if (!normalizedWorkspaceRoot.startsWith(repositoryPrefix)) return null;
return normalizedWorkspaceRoot
.slice(repositoryPrefix.length)
.split(/[\\/]+/)
.filter(Boolean);
}
export function resolveDiffPathForWorkspace(input: {
readonly filePath: string;
readonly workspaceRoot: string | undefined;
readonly repositoryRoot: string | undefined;
}): string | null {
// Claude and ACP tool-call diffs name files by absolute path.
if (isAbsolutePath(input.filePath)) {
const relativePath = workspaceRelativeFilePath(input.filePath, input.workspaceRoot);
const segments = relativePath === null ? null : normalizedRelativePathSegments(relativePath);
return segments ? segments.join("/") : null;
}
const fileSegments = normalizedRelativePathSegments(input.filePath);
if (!fileSegments) return null;
const workspaceSegments = repositoryRelativeWorkspaceSegments(
input.workspaceRoot,
input.repositoryRoot,
);
if (!workspaceSegments || workspaceSegments.length === 0) {
return fileSegments.join("/");
}
const caseInsensitive = input.repositoryRoot
? isWindowsAbsolutePath(input.repositoryRoot)
: false;
const belongsToWorkspace = workspaceSegments.every((segment, index) => {
const candidate = fileSegments[index];
if (candidate === undefined) return false;
return caseInsensitive ? candidate.toLowerCase() === segment : candidate === segment;
});
if (!belongsToWorkspace) return null;
const relativeSegments = fileSegments.slice(workspaceSegments.length);
return relativeSegments.length > 0 ? relativeSegments.join("/") : null;
}
export function openDiffFilePrimaryAction({
threadRef,
filePath,
activeCwd,
repositoryRoot,
openInEditor,
}: OpenDiffFilePrimaryActionInput): void {
const workspaceFilePath = resolveDiffPathForWorkspace({
filePath,
workspaceRoot: activeCwd,
repositoryRoot,
});
if (!workspaceFilePath) return;
if (threadRef) {
useRightPanelStore.getState().openFile(threadRef, workspaceFilePath);
return;
}
openInEditor(activeCwd ? resolvePathLinkTarget(workspaceFilePath, activeCwd) : workspaceFilePath);
}