import type { ScopedThreadRef } from "@t3tools/contracts"; import { isWindowsAbsolutePath, normalizeProjectPathForComparison } 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 | 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 | 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 { 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); }