Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/mobile/src/lib/enrichedLinkPresentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ describe("Enriched link presentation", () => {
"https://example.com/app.tsx",
"x + y",
"README.md",
"Dockerfile",
"docker/Dockerfiles",
"/app/settings",
]) {
expect(ENRICHED_INLINE_FILE_LINK_REGEX.test(text), text).toBe(false);
Expand Down
11 changes: 9 additions & 2 deletions apps/mobile/src/lib/enrichedLinkPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
parseComposerContextHref,
} from "@t3tools/shared/composerContextReferences";
import { hasThreadLinks, parseThreadLinkHref } from "@t3tools/shared/threadLinks";
import { EXTENSIONLESS_FILE_NAMES } from "@t3tools/shared/markdownLinks";

export function enrichedContextLinkPresentation(
href: string,
Expand Down Expand Up @@ -91,8 +92,14 @@ export function enrichedLinkVariantPattern(url: string) {
return `^${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
}

export const ENRICHED_INLINE_FILE_LINK_REGEX =
/^(?!\S*:\/\/)(?:(?:\.{1,2}[\\/]|~[\\/]|\/(?:Users|home|tmp|var|etc|opt|mnt|Volumes|private|root|usr|bin|sbin|lib|lib64|srv|dev|proc|sys|run|boot|media|workspace|workspaces)\/|[A-Za-z]:[\\/])[^\s`]+|(?:[\p{L}\p{M}\p{N}._-]+[\\/])+[\p{L}\p{M}\p{N}._-]+\.[A-Za-z0-9_-]+(?::\d+(?::\d+|-\d+)?)?|[\p{L}\p{M}\p{N}._-]+\.[A-Za-z0-9_-]+:\d+(?::\d+|-\d+)?)$/u;
const EXTENSIONLESS_FILE_NAME_PATTERN = [...EXTENSIONLESS_FILE_NAMES]
.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|");

export const ENRICHED_INLINE_FILE_LINK_REGEX = new RegExp(
String.raw`^(?!\S*:\/\/)(?:(?:\.{1,2}[\\/]|~[\\/]|\/(?:Users|home|tmp|var|etc|opt|mnt|Volumes|private|root|usr|bin|sbin|lib|lib64|srv|dev|proc|sys|run|boot|media|workspace|workspaces)\/|[A-Za-z]:[\\/])[^\s\x60]+|(?:[\p{L}\p{M}\p{N}._-]+[\\/])+(?:[\p{L}\p{M}\p{N}._-]+\.[A-Za-z0-9_-]+|(?:${EXTENSIONLESS_FILE_NAME_PATTERN}))(?::\d+(?::\d+|-\d+)?)?|[\p{L}\p{M}\p{N}._-]+\.[A-Za-z0-9_-]+:\d+(?::\d+|-\d+)?)$`,
"u",
);

export function enrichedSkillLinkRegex(skills?: ReadonlyArray<SelectableMarkdownSkill>) {
const names = [...new Set(skills?.map((skill) => skill.name) ?? [])].filter(Boolean);
Expand Down
8 changes: 7 additions & 1 deletion apps/mobile/src/lib/markdownInlineFileLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ describe("inline code that names a file", () => {
expect(rewrite("See `src/math.ts:2` and `./README.md`.")).toBe(
"See [src/math.ts:2](<src/math.ts:2>) and [./README.md](<./README.md>).",
);
for (const path of ["src/數學.ts:2", "數學/math.ts:2", "數學.ts:2", "src/數學.ts:2-4"]) {
for (const path of [
"src/數學.ts:2",
"數學/math.ts:2",
"數學.ts:2",
"src/數學.ts:2-4",
"docker/Dockerfile",
]) {
expect(rewrite(`See \`${path}\`.`), path).toBe(`See [${path}](<${path}>).`);
}
});
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/markdownLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("inlineCodeFilePathCandidate", () => {
["origin/main", null],
["z-ai/glm-5.3", null],
["share/man/ls.1", "share/man/ls.1"],
["docker/Dockerfile", "docker/Dockerfile"],
["src/main.ts/", null],
["./patches/", "./patches/"],
// regression: #30757 an agent's shortened path is not a file
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/markdownLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const POSIX_FILE_ROOT_PREFIXES = [
"/workspaces/",
] as const;

const EXTENSIONLESS_FILE_NAMES = new Set([
export const EXTENSIONLESS_FILE_NAMES: ReadonlySet<string> = new Set([
"Makefile",
"makefile",
"GNUmakefile",
Expand Down Expand Up @@ -202,7 +202,7 @@ export function inlineCodeFilePathCandidate(codeText: string): string | null {
.at(-1) ?? "";
if (VERSION_SUFFIX_PATTERN.test(basename)) return null;
if (!FILE_EXTENSION_PATTERN.test(basename)) {
if (!hasPosition) return null;
if (!hasPosition) return EXTENSIONLESS_FILE_NAMES.has(basename) ? candidate : null;
// A `:line` suffix alone does not make `I/W217:220` a file reference.
if (
withoutPosition.includes("/") &&
Expand Down
Loading