1import type { AssetResource, ProjectReadFileError, ScopedThreadRef } from "@t3tools/contracts";
2import { isAbsolutePath, workspaceRelativeFilePath } from "@t3tools/shared/path";
3
4/** Resolve workspace links before choosing between the explorer and a file preview. */
5export function resolveFilePreviewPath(path: string | null, cwd: string): string | null {
6 if (path === null) return null;
7 return path === "." || workspaceRelativeFilePath(path, cwd) === "." ? null : path;
8}
9
10export const isMarkdownPreviewFile = (path: string): boolean => /\.(?:md|mdx)$/i.test(path);
11
12/** Describe existing failure codes without exposing the underlying platform cause. */
13export function filePreviewReadErrorMessage(error: ProjectReadFileError): string {
14 switch (error.failure) {
15 case "path_not_file":
16 return "The path is a directory or special file, not a regular file.";
17 case "binary_file":
18 return "The file is binary and cannot be displayed as text.";
19 case "workspace_path_outside_root":
20 return "The requested path is outside the workspace.";
21 case "resolved_path_outside_root":
22 return "The path resolves to a location outside the workspace.";
23 case "operation_failed":
24 // A realpath failure can mean a missing path, permissions, or another I/O error.
25 return error.operation === "realpath-workspace-root"
26 ? "The workspace folder could not be accessed."
27 : "The file could not be accessed or read. It may be missing or inaccessible.";
28 default:
29 return error.message;
30 }
31}
32
33export function shouldShowFileExplorer(input: {
34 readonly relativePath: string | null;
35 readonly explorerOpen: boolean;
36 readonly attachmentOpen: boolean;
37}): boolean {
38 if (input.attachmentOpen || (input.relativePath && isAbsolutePath(input.relativePath))) {
39 return false;
40 }
41 return input.explorerOpen || input.relativePath === null;
42}
43
44/**
45 * The asset for a file the panel shows. A draft has no thread on the server
46 * yet, so it names its workspace root instead of a thread to resolve one from.
47 */
48export function workspaceAssetResource(input: {
49 readonly kind: "workspace-file" | "media-file";
50 readonly threadRef: ScopedThreadRef;
51 readonly draft: boolean;
52 readonly workspaceRoot: string;
53 readonly absolutePath: string;
54}): AssetResource {
55 if (input.draft) {
56 return { _tag: "draft-workspace-file", cwd: input.workspaceRoot, path: input.absolutePath };
57 }
58 return { _tag: input.kind, threadId: input.threadRef.threadId, path: input.absolutePath };
59}