| author | |
| committer | |
| log | 8faf031c2dac4f147c43fe32e326b76678c20c94 |
| tree | 76dd7e1da726673ffd236457e055153cee7aaabd |
| parent | ed2bdbb278c4fedf6ce79d4925908b759f7fd992 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>5 files changed, 83 insertions(+), 5 deletions(-)
apps/web/src/filePathDisplay.test.ts+6| ... | ... | @@ -38,4 +38,10 @@ describe("formatWorkspaceRelativePath", () => { |
| 38 | 38 | ), |
| 39 | 39 | ).toBe("t3code/apps/web/src/session-logic.ts:501:9"); |
| 40 | 40 | }); |
| 41 | ||
| 42 | it("keeps double-slash POSIX paths case-sensitive", () => { | |
| 43 | expect(formatWorkspaceRelativePath("//tmp/project/probe.txt", "//tmp/Project")).toBe( | |
| 44 | "//tmp/project/probe.txt", | |
| 45 | ); | |
| 46 | }); | |
| 41 | 47 | }); |
apps/web/src/filePathDisplay.ts+7-3| ... | ... | @@ -4,6 +4,7 @@ import { |
| 4 | 4 | splitFilePathPosition, |
| 5 | 5 | stripSlashPrefixedWindowsDrive, |
| 6 | 6 | } from "@t3tools/client-runtime/markdown-links"; |
| 7 | import { isWindowsAbsolutePath } from "@t3tools/shared/path"; | |
| 7 | 8 | |
| 8 | 9 | function normalizePathSeparators(path: string): string { |
| 9 | 10 | return path.replaceAll("\\", "/"); |
| ... | ... | @@ -30,10 +31,13 @@ export function formatWorkspaceRelativePath( |
| 30 | 31 | normalizePathSeparators(trimTrailingPathSeparators(workspaceRoot)), |
| 31 | 32 | ); |
| 32 | 33 | const workspaceLabel = fileBasename(normalizedWorkspaceRoot); |
| 33 | const pathForCompare = normalizedPath.toLowerCase(); | |
| 34 | const workspaceForCompare = normalizedWorkspaceRoot.toLowerCase(); | |
| 34 | const caseInsensitive = isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(workspaceRoot)); | |
| 35 | const pathForCompare = caseInsensitive ? normalizedPath.toLowerCase() : normalizedPath; | |
| 36 | const workspaceForCompare = caseInsensitive | |
| 37 | ? normalizedWorkspaceRoot.toLowerCase() | |
| 38 | : normalizedWorkspaceRoot; | |
| 35 | 39 | const workspaceWithSeparator = `${workspaceForCompare}/`; |
| 36 | const workspaceLabelWithSeparator = `${workspaceLabel.toLowerCase()}/`; | |
| 40 | const workspaceLabelWithSeparator = `${caseInsensitive ? workspaceLabel.toLowerCase() : workspaceLabel}/`; | |
| 37 | 41 | |
| 38 | 42 | if (pathForCompare === workspaceForCompare) { |
| 39 | 43 | displayPath = workspaceLabel; |
apps/web/src/markdown-links.test.ts+55| ... | ... | @@ -254,6 +254,61 @@ describe("resolveMarkdownFileLinkTarget", () => { |
| 254 | 254 | }); |
| 255 | 255 | }); |
| 256 | 256 | |
| 257 | it("does not classify a case-distinct POSIX sibling as a workspace file", () => { | |
| 258 | expect( | |
| 259 | resolveMarkdownFileLinkMeta( | |
| 260 | "/tmp/t3code-case-test/project/probe.txt", | |
| 261 | "/tmp/t3code-case-test/Project", | |
| 262 | ), | |
| 263 | ).toMatchObject({ | |
| 264 | displayPath: "/tmp/t3code-case-test/project/probe.txt", | |
| 265 | workspaceRelativePath: null, | |
| 266 | }); | |
| 267 | }); | |
| 268 | ||
| 269 | it("keeps Windows workspace comparisons case-insensitive", () => { | |
| 270 | expect( | |
| 271 | resolveMarkdownFileLinkMeta("C:/Users/MIKE/Project/src/main.ts", "c:/users/mike/project"), | |
| 272 | ).toMatchObject({ | |
| 273 | displayPath: "project/src/main.ts", | |
| 274 | workspaceRelativePath: "src/main.ts", | |
| 275 | }); | |
| 276 | }); | |
| 277 | ||
| 278 | it("keeps drive-root workspace comparisons case-insensitive", () => { | |
| 279 | expect(resolveMarkdownFileLinkMeta("C:/Users/MIKE/project.ts", "c:/")).toMatchObject({ | |
| 280 | displayPath: "c:/Users/MIKE/project.ts", | |
| 281 | workspaceRelativePath: "Users/MIKE/project.ts", | |
| 282 | }); | |
| 283 | }); | |
| 284 | ||
| 285 | it("keeps backslash UNC workspace comparisons case-insensitive", () => { | |
| 286 | expect( | |
| 287 | resolveMarkdownFileLinkMeta( | |
| 288 | "\\\\server\\share\\PROJECT\\src\\main.ts", | |
| 289 | "\\\\Server\\Share\\Project", | |
| 290 | ), | |
| 291 | ).toMatchObject({ | |
| 292 | displayPath: "Project/src/main.ts", | |
| 293 | workspaceRelativePath: "src/main.ts", | |
| 294 | }); | |
| 295 | }); | |
| 296 | ||
| 297 | it.each([ | |
| 298 | ["/tmp/repo/file.ts", "/", "tmp/repo/file.ts"], | |
| 299 | ["C:/Users/MIKE/file.ts", "c:/", "Users/MIKE/file.ts"], | |
| 300 | ["\\\\server\\SHARE\\file.ts", "\\\\Server\\Share\\", "file.ts"], | |
| 301 | ["/tmp/repo/file.ts%20", "/tmp/repo", "file.ts "], | |
| 302 | ])("preserves the preview target for %s in workspace %s", (href, cwd, workspaceRelativePath) => { | |
| 303 | expect(resolveMarkdownFileLinkMeta(href, cwd)).toMatchObject({ workspaceRelativePath }); | |
| 304 | }); | |
| 305 | ||
| 306 | it("keeps an encoded final space in the absolute target", () => { | |
| 307 | expect(resolveMarkdownFileLinkTarget("/tmp/repo/file.ts%20", "/tmp/repo")).toBe( | |
| 308 | "/tmp/repo/file.ts ", | |
| 309 | ); | |
| 310 | }); | |
| 311 | ||
| 257 | 312 | it("normalizes slash-prefixed windows drive paths before resolving", () => { |
| 258 | 313 | expect( |
| 259 | 314 | resolveMarkdownFileLinkTarget( |
packages/client-runtime/src/markdownLinks.test.ts+11-1| ... | ... | @@ -160,7 +160,17 @@ describe("workspaceRelativeFilePath", () => { |
| 160 | 160 | ["/repo/project/src/main.ts", "/repo/project/", "src/main.ts"], |
| 161 | 161 | ["C:\\Users\\mike\\t3code\\apps\\web\\a.ts", "C:/Users/mike/t3code", "apps/web/a.ts"], |
| 162 | 162 | ["/C:/Users/mike/t3code/apps/web/a.ts", "C:/Users/mike/t3code", "apps/web/a.ts"], |
| 163 | ["/Repo/Project/src/main.ts", "/repo/project", "src/main.ts"], | |
| 163 | ["/Repo/Project/src/main.ts", "/repo/project", null], | |
| 164 | ["/tmp/case/project/probe.txt", "/tmp/case/Project", null], | |
| 165 | ["//tmp/case/project/probe.txt", "//tmp/case/Project", null], | |
| 166 | ["/tmp/case/Project/probe.txt", "/tmp/case/Project", "probe.txt"], | |
| 167 | ["C:/USERS/mike/t3code/main.ts", "c:/users/MIKE/t3code", "main.ts"], | |
| 168 | ["/C:/USERS/mike/t3code/main.ts", "/c:/users/MIKE/t3code", "main.ts"], | |
| 169 | ["\\\\server\\share\\PROJECT\\main.ts", "\\\\Server\\Share\\Project", "main.ts"], | |
| 170 | ["/tmp/repo/file.ts", "/", "tmp/repo/file.ts"], | |
| 171 | ["C:/Users/MIKE/main.ts", "c:/", "Users/MIKE/main.ts"], | |
| 172 | ["\\\\server\\SHARE\\file.ts", "\\\\Server\\Share\\", "file.ts"], | |
| 173 | ["/tmp/repo/file.ts ", "/tmp/repo", "file.ts "], | |
| 164 | 174 | ["/tmp/report.ts", "/repo/project", null], |
| 165 | 175 | ["/repo/project-two/a.ts", "/repo/project", null], |
| 166 | 176 | ["/repo/project/a.ts", undefined, null], |
packages/client-runtime/src/markdownLinks.ts+4-1| ... | ... | @@ -342,6 +342,9 @@ export function workspaceRelativeFilePath( |
| 342 | 342 | const normalizedRoot = stripSlashPrefixedWindowsDrive( |
| 343 | 343 | workspaceRoot.replaceAll("\\", "/"), |
| 344 | 344 | ).replace(/\/+$/, ""); |
| 345 | if (!normalizedPath.toLowerCase().startsWith(`${normalizedRoot.toLowerCase()}/`)) return null; | |
| 345 | const caseInsensitive = isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(workspaceRoot)); | |
| 346 | const pathForCompare = caseInsensitive ? normalizedPath.toLowerCase() : normalizedPath; | |
| 347 | const rootForCompare = caseInsensitive ? normalizedRoot.toLowerCase() : normalizedRoot; | |
| 348 | if (!pathForCompare.startsWith(`${rootForCompare}/`)) return null; | |
| 346 | 349 | return normalizedPath.slice(normalizedRoot.length + 1); |
| 347 | 350 | } |