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
@@ -6,7 +6,7 @@ import {
buildDeterministicWorkspaceTabId,
normalizeWorkspaceTabTarget,
workspaceTabTargetsEqual,
} from "@/utils/workspace-tab-identity";
} from "@/workspace-tabs/identity";
export interface SplitPane {
id: string;
@@ -1051,19 +1051,45 @@ function insertNewTabIntoFocusedPane(input: {
};
}
function findExistingTabForTarget(root: SplitNodeInternal, target: WorkspaceTabTarget) {
const targetTabId = buildDeterministicWorkspaceTabId(target);
return (
collectAllTabs(root).find(
(tab) => tab.tabId === targetTabId || workspaceTabTargetsEqual(tab.target, target),
) ?? null
);
}
function updateExistingTabTarget(
layout: { root: SplitNodeInternal; focusedPaneId: string | null },
tab: WorkspaceTab,
target: WorkspaceTabTarget,
): { root: SplitNodeInternal; focusedPaneId: string | null } {
if (workspaceTabTargetsEqual(tab.target, target)) {
return layout;
}
return {
...layout,
root: replaceTabInTree(layout.root, {
tabId: tab.tabId,
nextTabId: tab.tabId,
target,
}),
};
}
export function openTabInLayoutFocused(input: OpenTabInLayoutInput): OpenTabInLayoutResult {
const layout = asInternalLayout(input.layout);
const existingTab = collectAllTabs(layout.root).find((tab) =>
workspaceTabTargetsEqual(tab.target, input.target),
);
const existingTab = findExistingTabForTarget(layout.root, input.target);
if (existingTab) {
const nextLayout = updateExistingTabTarget(layout, existingTab, input.target);
return {
tabId: existingTab.tabId,
layout:
focusTabInLayout({
layout,
layout: nextLayout,
tabId: existingTab.tabId,
}) ?? input.layout,
}) ?? nextLayout,
};
}
@@ -1072,11 +1098,12 @@ export function openTabInLayoutFocused(input: OpenTabInLayoutInput): OpenTabInLa
export function openTabInLayoutBackground(input: OpenTabInLayoutInput): OpenTabInLayoutResult {
const layout = asInternalLayout(input.layout);
const existingTab = collectAllTabs(layout.root).find((tab) =>
workspaceTabTargetsEqual(tab.target, input.target),
);
const existingTab = findExistingTabForTarget(layout.root, input.target);
if (existingTab) {
return { tabId: existingTab.tabId, layout: input.layout };
return {
tabId: existingTab.tabId,
layout: updateExistingTabTarget(layout, existingTab, input.target),
};
}
return insertNewTabIntoFocusedPane({ ...input, focus: false });
@@ -353,6 +353,39 @@ describe("workspace-layout-store actions", () => {
]);
});
it("updates an existing file tab when opening the same path at a new line range", () => {
const workspaceKey = createWorkspaceKey();
const store = workspaceLayoutStore.getState();
const firstTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/a.ts",
lineStart: 5,
});
const secondTabId = store.openTabFocused(workspaceKey, {
kind: "file",
path: "/repo/worktree/a.ts",
lineStart: 10,
lineEnd: 12,
});
const layout = workspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
expect(firstTabId).toBe("file_/repo/worktree/a.ts");
expect(secondTabId).toBe(firstTabId);
expect(collectAllTabs(layout.root)).toEqual([
{
tabId: "file_/repo/worktree/a.ts",
target: {
kind: "file",
path: "/repo/worktree/a.ts",
lineStart: 10,
lineEnd: 12,
},
createdAt: expect.any(Number),
},
]);
});
it("openTabInBackground inserts a tab without stealing focus", () => {
const workspaceKey = createWorkspaceKey();
const store = workspaceLayoutStore.getState();
@@ -44,7 +44,7 @@ import {
type WorkspaceTabSnapshot,
type WorkspaceLayout,
} from "@/stores/workspace-layout-actions";
import { normalizeWorkspaceTabTarget } from "@/utils/workspace-tab-identity";
import { normalizeWorkspaceTabTarget } from "@/workspace-tabs/identity";
export { buildWorkspaceTabPersistenceKey };
export {
@@ -7,7 +7,8 @@ import {
normalizeWorkspaceDraftTabSetup,
normalizeWorkspaceTabTarget,
workspaceTabTargetsEqual,
} from "@/utils/workspace-tab-identity";
} from "@/workspace-tabs/identity";
import type { WorkspaceFileTabTarget } from "@/workspace/file-open";
export interface WorkspaceDraftTabSetup {
provider: AgentProvider;
@@ -23,7 +24,7 @@ export type WorkspaceTabTarget =
| { kind: "agent"; agentId: string }
| { kind: "terminal"; terminalId: string }
| { kind: "browser"; browserId: string }
| { kind: "file"; path: string }
| WorkspaceFileTabTarget
| { kind: "setup"; workspaceId: string };
export interface WorkspaceTab {
@@ -168,7 +169,12 @@ function coerceWorkspaceTabTarget(raw: Record<string, unknown>): WorkspaceTabTar
return normalizeWorkspaceTabTarget({ kind: "browser", browserId: raw.browserId });
}
if (kind === "file" && typeof raw.path === "string") {
return normalizeWorkspaceTabTarget({ kind: "file", path: raw.path });
return normalizeWorkspaceTabTarget({
kind: "file",
path: raw.path,
lineStart: typeof raw.lineStart === "number" ? raw.lineStart : undefined,
lineEnd: typeof raw.lineEnd === "number" ? raw.lineEnd : undefined,
});
}
if (kind === "setup" && typeof raw.workspaceId === "string") {
return normalizeWorkspaceTabTarget({ kind: "setup", workspaceId: raw.workspaceId });