fix(app): open slash-prefixed Windows drive links

Normalize a slash before the drive only when the workspace identifies Windows path syntax. Otherwise the file preview selects a POSIX root and loses the workspace scope. Keep POSIX paths and existing decoding and line parsing intact.
This commit is contained in:
paseo-bot[bot]
2026-10-23 14:49:49 +02:00
parent 74dc764f0d
commit 1f49732c63
2 changed files with 52 additions and 2 deletions
@@ -1,4 +1,6 @@
import { describe, expect, it } from "vitest";
import { resolveFilePreviewReadTarget } from "@/file-explorer/preview-target";
import { resolveWorkspaceFilePaths } from "@/workspace/file-open";
import {
classifyAssistantFileLink,
normalizeInlinePathTarget,
@@ -611,3 +613,43 @@ describe("encoded filename line markers", () => {
},
);
});
describe("slash-prefixed drive links in Windows workspaces", () => {
it.each([
["/E:/demo/repo/tools/tool.vbs", "E:/demo/repo/tools/tool.vbs", undefined],
["/E:/demo/repo/docs/my%20file.md#L12", "E:/demo/repo/docs/my file.md", 12],
["/E:/demo/repo/docs/guide.md:12", "E:/demo/repo/docs/guide.md", 12],
["E:/demo/repo/tools/tool.vbs", "E:/demo/repo/tools/tool.vbs", undefined],
["file:///E:/demo/repo/tools/tool.vbs", "E:/demo/repo/tools/tool.vbs", undefined],
])("resolves %s to the workspace read target", (href, path, lineStart) => {
const workspaceRoot = "E:\\demo\\repo";
const target = parseAssistantFileLink(href, {
workspaceRoot,
decodeHref: true,
explicitLink: true,
});
expect(target).toMatchObject({ raw: href, path });
expect(target?.lineStart).toBe(lineStart);
expect(resolveFilePreviewReadTarget({ path: target!.path, workspaceRoot })).toEqual({
cwd: workspaceRoot,
path,
});
expect(resolveWorkspaceFilePaths({ path: target!.path, workspaceRoot })).toEqual({
absolutePath: path,
relativePath: path.slice("E:/demo/repo/".length),
});
});
it.each(["/home/demo/repo", undefined])(
"preserves POSIX paths with workspace %s",
(workspaceRoot) => {
expect(
parseAssistantFileLink("/E:/demo/repo/tools/tool.vbs", {
workspaceRoot,
decodeHref: true,
explicitLink: true,
})?.path,
).toBe("/E:/demo/repo/tools/tool.vbs");
},
);
});
+10 -2
View File
@@ -260,7 +260,7 @@ export function parseAssistantFileLink(
return fileUrlTarget;
}
const trimmed = value?.trim();
const trimmed = normalizeWindowsLinkPath(value?.trim(), options.workspaceRoot);
if (!trimmed) {
return null;
}
@@ -271,7 +271,7 @@ export function parseAssistantFileLink(
const inlinePathTarget = parseAssistantInlinePathLink(trimmed, options);
if (inlinePathTarget) {
return inlinePathTarget;
return { ...inlinePathTarget, raw: value };
}
const windowsTarget = parseWindowsFileLink(trimmed, value, options);
@@ -350,6 +350,14 @@ export function isFileLookingAssistantToken(value: string): boolean {
return isPlausibleAssistantLocalPath(path);
}
// A drive-based workspace identifies the target host's path syntax, even when
// the client runs elsewhere. Preserve the same spelling on POSIX hosts.
function normalizeWindowsLinkPath(value: string, workspaceRoot?: string): string {
return /^[A-Za-z]:[\\/]/.test(workspaceRoot?.trim() ?? "") && /^\/[A-Za-z]:[\\/]/.test(value)
? value.slice(1)
: value;
}
function parseWindowsFileLink(
trimmed: string,
raw: string,