Compare commits

...
Author SHA1 Message Date
l0ng-ai dff68f8527 perf(search): switch editors in the frame the Switcher closes
Releasing Ctrl in the Switcher (and picking from Recent Files / Recent
Locations) closed the popup, painted a frame of the old editor, and only
then opened the selection from a timeout. The popup now unmounts
synchronously (`flushSync`), gives focus back, and the selection opens in
the same task, so closing and switching paint together.

`pnpm perf` measures Ctrl+Tab between file tabs (`tabs.switcher*`) with a
p50 budget of two frames, and the agent tab switch budget is tightened.
2026-11-09 12:04:34 +08:00
4 changed files with 61 additions and 6 deletions

No files matched your search

+2 -1
View File
@@ -72,10 +72,11 @@
"tabs.openSmallMs": 100,
"tabs.switchP95Ms": 33,
"tabs.switcherP50Ms": 33,
"agentTabs.conversationNodes": 20000,
"agentTabs.rowsAlwaysRendered": 0,
"agentTabs.switchP95Ms": 66,
"agentTabs.switchP95Ms": 50,
"agentTabs.switchMainThreadMs": 50,
"agentTabs.switchTbtMs": 50,
"agentTabs.scrollJumps": 0,
+15 -1
View File
@@ -455,7 +455,21 @@ const tabs: Scenario = {
switches.push(await h.measure(() => tab.click(), ".cm-content", `// tab-${i}`));
}
}
return { openSmallMs, ...latencySummary(switches, "switch") };
// The Switcher (Ctrl+Tab) back and forth between the two most recent tabs.
const before = await h.counters();
const switcher: number[] = [];
for (let i = 0; i < 10; i++) {
const tag = i % 2 ? names.length - 1 : names.length - 2;
switcher.push(await h.measure(() => h.pressAction("navigate.switcher"), ".cm-content", `// tab-${tag}`));
await h.page.waitForTimeout(150);
}
const work = Harness.delta(before, await h.counters());
return {
openSmallMs,
...latencySummary(switches, "switch"),
...latencySummary(switcher, "switcher"),
switcherMainThreadMs: work.taskMs / switcher.length,
};
},
};
+37 -1
View File
@@ -55,7 +55,7 @@ vi.mock("../find/windowStore", async (orig) => ({
const contributions = await import("@/workbench/contributions");
const { commands } = await import("@/platform/ipc");
const { RecentPopupsOverlay } = await import("../recent/RecentPopups");
const { showRecentPopup, hideRecentPopup, useRecentPopup } = await import("../recent/store");
const { cycleSwitcher, showRecentPopup, hideRecentPopup, useRecentPopup } = await import("../recent/store");
const { openLocation } = await import("../common/open");
const { usePlatform } = await import("@/platform/platformInfo");
const { allActions } = await import("@/platform/actions");
@@ -476,6 +476,42 @@ describe("Recent Files multi-selection", () => {
});
});
describe("Switcher", () => {
afterEach(() => vi.restoreAllMocks());
it("opens the selection when Ctrl is released, once the popup gave focus back, in the same task", async () => {
const tab = (key: string, lastActive: number) => ({
input: { kind: "agent-session", key },
title: key,
icon: "toolwindow-agents",
lastActive,
});
const seen: Array<{ key: string; popup: boolean; focus: Element | null }> = [];
vi.spyOn(commands, "recentFiles").mockResolvedValue({ status: "ok", data: [] });
vi.spyOn(contributions, "maybeEditorService").mockReturnValue({
openTabs: () => [tab("task-b", 2), tab("task-a", 1)],
openPaths: () => [],
activePath: () => null,
openInput: async (input: { key: string }) => {
seen.push({ key: input.key, popup: !!screen.queryByRole("dialog"), focus: document.activeElement });
},
} as unknown as import("@/workbench/contributions").EditorService);
render(
<>
<button type="button">editor</button>
<RecentPopupsOverlay />
</>,
);
const editor = screen.getByRole("button", { name: "editor" });
editor.focus();
act(() => cycleSwitcher(1));
await tick();
expect(screen.getByRole("dialog", { name: "Switcher" })).toBeInTheDocument();
act(() => void fireEvent.keyUp(window, { key: "Control" }));
expect(seen).toEqual([{ key: "task-a", popup: false, focus: editor }]);
});
});
describe("Find in Files dialog", () => {
it("streams results and highlights the matches while replacing, as IDEA", async () => {
render(<FindInFilesOverlay />);
+7 -3
View File
@@ -1,5 +1,6 @@
import { useFilePresentation } from "@/platform/filePresentation";
import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type MouseEvent as ReactMouseEvent } from "react";
import { flushSync } from "react-dom";
import clsx from "clsx";
import { editorsFor, getNavigationHistory } from "@/editor/api";
import { call, commands, type OutlineItem } from "@/platform/ipc";
@@ -61,10 +62,13 @@ export function RecentPopupsOverlay() {
const NAV_KEYS = ["ArrowDown", "ArrowUp", "PageDown", "PageUp"];
/** Defers work until the dialog unmounted and restored focus. */
/**
* Runs `fn` once the dialog has unmounted and given focus back, in the same frame: the
* popup closing and what it opens paint together, without a frame of the old editor between.
*/
const afterClose = (fn: () => void) => {
hideRecentPopup();
setTimeout(fn, 0);
flushSync(hideRecentPopup);
fn();
};
// ---------------------------------------------------------------------------