Skip to content
Source
Sign inSign up

markdown-links.ts

110 lines3.4 KB

1import { isLocalEditorFileUrl } from "@t3tools/contracts";
2import { fileBasename, isAbsolutePath, workspaceRelativeFilePath } from "@t3tools/shared/path";
3import {
4 inlineCodeFilePathCandidate,
5 isRelativeFilePath,
6 normalizeMarkdownLinkDestination,
7 parseMarkdownFileLink,
8 resolveMarkdownFileLinkTarget,
9} from "@t3tools/shared/markdownLinks";
10import { parseFileUrlHref, splitFilePathPosition } from "@t3tools/shared/fileLinks";
11​
12import { formatWorkspaceRelativePath } from "./filePathDisplay";
13import { isTerminalLinkActivation } from "./terminal-links";
14​
15export interface MarkdownFileLinkMeta {
16 filePath: string;
17 workspaceLookupPath: string | null;
18 targetPath: string;
19 displayPath: string;
20 workspaceRelativePath: string | null;
21 basename: string;
22 line?: number;
23 column?: number;
24 endLine?: number;
26​
27export function shouldOpenMarkdownFileLinkInEditor(
28 event: Pick<MouseEvent, "metaKey" | "ctrlKey">,
29 platform?: string,
30): boolean {
31 return isTerminalLinkActivation(event, platform);
33​
34export function shouldOpenMarkdownFileLinkInBrowserByDefault(path: string): boolean {
35 return /\.pdf$/i.test(path.split(/[?#]/, 1)[0] ?? "");
37​
38export function rewriteMarkdownFileUriHref(href: string | undefined): string | null {
39 if (!href) return null;
40 const target = parseFileUrlHref(normalizeMarkdownLinkDestination(href));
41 return target ? `${target.path}${target.hash}` : null;
43​
44export function isLocalEditorFileHref(href: string): boolean {
45 try {
46 return isLocalEditorFileUrl(new URL(href));
47 } catch {
48 return false;
49 }
51​
52export function resolveInlineCodeFileLinkMeta(
53 codeText: string,
54 cwd?: string,
55 baseDir: string | undefined = cwd,
56): MarkdownFileLinkMeta | null {
57 const candidate = inlineCodeFilePathCandidate(codeText);
58 if (candidate === null) return null;
59​
60 return resolveMarkdownFileLinkMeta(
61 candidate,
62 cwd,
63 inlineCodePathNamesFromWorkspaceRoot(candidate, cwd, baseDir) ? cwd : baseDir,
64 );
66​
67function inlineCodePathNamesFromWorkspaceRoot(
68 candidate: string,
69 cwd: string | undefined,
70 baseDir: string | undefined,
71): boolean {
72 if (!cwd || !baseDir || !isRelativeFilePath(candidate)) return false;
73 if (/^(?:~|\.{1,2})\//.test(candidate)) return false;
74 if (!splitFilePathPosition(candidate).path.includes("/")) return false;
75 return workspaceRelativeFilePath(baseDir, cwd) !== null;
77​
78export function resolveMarkdownFileLinkMeta(
79 href: string | undefined,
80 cwd?: string,
81 baseDir: string | undefined = cwd,
82): MarkdownFileLinkMeta | null {
83 const targetPath = resolveMarkdownFileLinkTarget(href, cwd, baseDir);
84 if (!targetPath) return null;
85 const source = href ? parseMarkdownFileLink(href) : null;
86 return {
87 ...buildFileLinkMetaFromTarget(targetPath, cwd),
88 ...(source?.endLine !== undefined ? { endLine: source.endLine } : {}),
89 workspaceLookupPath:
90 source !== null &&
91 !isAbsolutePath(source.path) &&
92 baseDir !== undefined &&
93 workspaceRelativeFilePath(baseDir, cwd) === "."
94 ? source.path
95 : null,
96 };
98​
99function buildFileLinkMetaFromTarget(targetPath: string, cwd?: string) {
100 const { path, line, column } = splitFilePathPosition(targetPath);
101 return {
102 filePath: path,
103 targetPath,
104 displayPath: formatWorkspaceRelativePath(targetPath, cwd),
105 workspaceRelativePath: workspaceRelativeFilePath(path, cwd),
106 basename: fileBasename(path),
107 ...(line !== undefined ? { line } : {}),
108 ...(column !== undefined ? { column } : {}),
109 };