Support line ranges in assistant file links (#1067)

This commit is contained in:
Mohamed Boudra
2026-05-17 22:55:46 +08:00
committed by GitHub
parent efc9c2d345
commit ee44b536f5
29 changed files with 504 additions and 115 deletions
@@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import {
createWorkspaceFileTabTarget,
normalizeWorkspaceFileLocation,
workspaceFileLocationsEqual,
} from ".";
describe("normalizeWorkspaceFileLocation", () => {
it("normalizes paths and valid line ranges", () => {
expect(
normalizeWorkspaceFileLocation({
path: "src\\app.ts",
lineStart: 12.8,
lineEnd: 20.2,
}),
).toEqual({
path: "src/app.ts",
lineStart: 12,
lineEnd: 20,
});
});
it("drops invalid or backwards line ranges", () => {
expect(normalizeWorkspaceFileLocation({ path: "src/app.ts", lineStart: -1 })).toEqual({
path: "src/app.ts",
});
expect(
normalizeWorkspaceFileLocation({ path: "src/app.ts", lineStart: 20, lineEnd: 12 }),
).toEqual({
path: "src/app.ts",
lineStart: 20,
});
});
it("rejects empty paths", () => {
expect(normalizeWorkspaceFileLocation({ path: " " })).toBeNull();
});
});
describe("workspace file tab targets", () => {
it("keeps file tab identity separate from line selection", () => {
expect(createWorkspaceFileTabTarget({ path: "src/app.ts", lineStart: 12 })).toEqual({
kind: "file",
path: "src/app.ts",
lineStart: 12,
});
});
it("compares full location equality", () => {
expect(
workspaceFileLocationsEqual(
{ path: "src/app.ts", lineStart: 12 },
{ path: "src/app.ts", lineStart: 12 },
),
).toBe(true);
expect(
workspaceFileLocationsEqual(
{ path: "src/app.ts", lineStart: 12 },
{ path: "src/app.ts", lineStart: 13 },
),
).toBe(false);
});
});
@@ -0,0 +1,59 @@
export type OpenFileDisposition = "main" | "side";
export interface WorkspaceFileLocation {
path: string;
lineStart?: number;
lineEnd?: number;
}
export type WorkspaceFileTabTarget = { kind: "file" } & WorkspaceFileLocation;
export interface WorkspaceFileOpenRequest {
location: WorkspaceFileLocation;
disposition: OpenFileDisposition;
}
export function normalizeWorkspaceFileLocation(
location: WorkspaceFileLocation | null | undefined,
): WorkspaceFileLocation | null {
if (!location) {
return null;
}
const path = location.path.trim().replace(/\\/g, "/");
if (!path) {
return null;
}
const lineStart = normalizeLineNumber(location.lineStart);
const lineEnd = normalizeLineNumber(location.lineEnd);
return {
path,
...(lineStart ? { lineStart } : {}),
...(lineStart && lineEnd && lineEnd >= lineStart ? { lineEnd } : {}),
};
}
export function workspaceFileLocationsEqual(
left: WorkspaceFileLocation,
right: WorkspaceFileLocation,
): boolean {
return (
left.path === right.path && left.lineStart === right.lineStart && left.lineEnd === right.lineEnd
);
}
export function createWorkspaceFileTabTarget(
location: WorkspaceFileLocation,
): WorkspaceFileTabTarget {
return {
kind: "file",
...location,
};
}
function normalizeLineNumber(value: number | null | undefined): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value > 0
? Math.floor(value)
: undefined;
}