Support line ranges in assistant file links (#1067)
This commit is contained in:
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user