feat: add tab drop preview indicator for split pane drag-and-drop

This commit is contained in:
Mohamed Boudra
2026-03-18 20:09:28 +07:00
parent b763d4358e
commit ed9a15c0bd
7 changed files with 329 additions and 38 deletions
@@ -92,6 +92,19 @@ describe("parseAssistantFileLink", () => {
});
});
it("parses absolute POSIX hrefs that use :line:column suffixes", () => {
expect(
parseAssistantFileLink("/Users/test/project/src/app.tsx:33:1", {
workspaceRoot: "/Users/test/project",
})
).toEqual({
raw: "/Users/test/project/src/app.tsx:33:1",
path: "/Users/test/project/src/app.tsx",
lineStart: 33,
lineEnd: undefined,
});
});
it("parses absolute Windows hrefs inside the active workspace", () => {
expect(
parseAssistantFileLink("C:/repo/src/app.tsx#L12-L20", {
@@ -105,6 +118,19 @@ describe("parseAssistantFileLink", () => {
});
});
it("parses absolute Windows hrefs that use :line:column suffixes", () => {
expect(
parseAssistantFileLink("C:/repo/src/app.tsx:12:4", {
workspaceRoot: "C:/repo",
})
).toEqual({
raw: "C:/repo/src/app.tsx:12:4",
path: "C:/repo/src/app.tsx",
lineStart: 12,
lineEnd: undefined,
});
});
it("allows file URLs even when they are outside the workspace root", () => {
expect(
parseAssistantFileLink("file:///tmp/outside.txt", {
+41 -4
View File
@@ -51,6 +51,38 @@ function parseLineFragment(value: string): Pick<InlinePathTarget, "lineStart" |
return { lineStart, lineEnd };
}
function parseTrailingLineSuffix(pathValue: string): {
path: string;
lineStart?: number;
lineEnd?: number;
} | null {
const match = pathValue.match(/^(.*?):([0-9]+)(?::([0-9]+))?$/);
if (!match) {
return null;
}
const normalizedPath = normalizePathToken(match[1] ?? "");
if (!normalizedPath) {
return null;
}
const lineStart = parseInt(match[2] ?? "", 10);
if (!Number.isFinite(lineStart) || lineStart <= 0) {
return null;
}
const column = match[3] ? parseInt(match[3], 10) : undefined;
if (column !== undefined && (!Number.isFinite(column) || column <= 0)) {
return null;
}
return {
path: normalizedPath,
lineStart,
lineEnd: undefined,
};
}
/**
* Strict VSCode-style markers only.
*
@@ -166,12 +198,15 @@ export function parseAssistantFileLink(
const windowsPathMatch = trimmed.match(/^([A-Za-z]:[\\/][^?#]*)(#[^?]+)?$/);
if (windowsPathMatch) {
const normalizedPath = normalizePathToken(windowsPathMatch[1] ?? "");
const rawWindowsPath = windowsPathMatch[1] ?? "";
const windowsPathWithLine = parseTrailingLineSuffix(rawWindowsPath);
const normalizedPath =
windowsPathWithLine?.path ?? normalizePathToken(rawWindowsPath);
if (!normalizedPath || !isAllowedAbsolutePath(normalizedPath, options.workspaceRoot)) {
return null;
}
const lines = parseLineFragment(windowsPathMatch[2] ?? "");
const lines = windowsPathWithLine ?? parseLineFragment(windowsPathMatch[2] ?? "");
if (!lines) {
return null;
}
@@ -194,7 +229,9 @@ export function parseAssistantFileLink(
return null;
}
const normalizedPath = normalizePathToken(decodeURIComponent(parsedUrl.pathname));
const rawPosixPath = decodeURIComponent(parsedUrl.pathname);
const posixPathWithLine = parseTrailingLineSuffix(rawPosixPath);
const normalizedPath = posixPathWithLine?.path ?? normalizePathToken(rawPosixPath);
if (!normalizedPath || !normalizedPath.startsWith("/")) {
return null;
}
@@ -203,7 +240,7 @@ export function parseAssistantFileLink(
return null;
}
const lines = parseLineFragment(parsedUrl.hash);
const lines = posixPathWithLine ?? parseLineFragment(parsedUrl.hash);
if (!lines) {
return null;
}