* perf(terminal): smooth layout drags and faster tab switching Defer xterm refit during split, sidebar, and host-tree drags while keeping pane containers in sync with live layout measurements. Refactor TerminalLayer into focused sections with TabBridge/memo optimizations and add the terminal host tree sidebar. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(terminal): keep side panels alive and guard session attach races Prevent terminal boot unmount from leaking backend sessions, keep SFTP/scripts/theme/AI state when switching side tabs, and defer heavy SFTP UI mount so first entry stays responsive. Co-authored-by: Cursor <cursoragent@cursor.com> * perf(terminal): reduce tab switch jank --------- Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
getTerminalPaneRenderSnapshot,
|
|
getTerminalPaneSnapshot,
|
|
HIDDEN_TERMINAL_PANE_SNAPSHOT,
|
|
parseTerminalPaneRenderSnapshot,
|
|
} from "./terminalPaneVisibility";
|
|
import type { Workspace } from "../types";
|
|
|
|
const createWorkspace = (
|
|
id: string,
|
|
sessionIds: string[],
|
|
options: Partial<Pick<Workspace, "viewMode" | "focusedSessionId">> = {},
|
|
): Workspace => ({
|
|
id,
|
|
title: id,
|
|
root: {
|
|
id: `${id}-root`,
|
|
type: "split",
|
|
direction: "vertical",
|
|
children: sessionIds.map((sessionId) => ({
|
|
id: `${id}-${sessionId}`,
|
|
type: "pane",
|
|
sessionId,
|
|
})),
|
|
},
|
|
viewMode: options.viewMode ?? "split",
|
|
focusedSessionId: options.focusedSessionId,
|
|
});
|
|
|
|
test("terminal pane snapshot stays hidden for panes outside both workspace tabs", () => {
|
|
const workspaceById = new Map<string, Workspace>([
|
|
["ws-a", createWorkspace("ws-a", ["a-1", "a-2"], { focusedSessionId: "a-1" })],
|
|
["ws-b", createWorkspace("ws-b", ["b-1", "b-2"], { focusedSessionId: "b-1" })],
|
|
["ws-c", createWorkspace("ws-c", ["c-1"])],
|
|
]);
|
|
|
|
const before = getTerminalPaneSnapshot({
|
|
activeTabId: "ws-a",
|
|
sessionId: "c-1",
|
|
sessionWorkspaceId: "ws-c",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
});
|
|
const after = getTerminalPaneSnapshot({
|
|
activeTabId: "ws-b",
|
|
sessionId: "c-1",
|
|
sessionWorkspaceId: "ws-c",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
});
|
|
|
|
assert.equal(before, HIDDEN_TERMINAL_PANE_SNAPSHOT);
|
|
assert.equal(after, before);
|
|
});
|
|
|
|
test("terminal pane snapshot distinguishes solo, split workspace, and focus workspace visibility", () => {
|
|
const workspaceById = new Map<string, Workspace>([
|
|
["ws-split", createWorkspace("ws-split", ["s-1", "s-2"], { focusedSessionId: "s-1" })],
|
|
["ws-focus", createWorkspace("ws-focus", ["f-1", "f-2"], {
|
|
viewMode: "focus",
|
|
focusedSessionId: "f-2",
|
|
})],
|
|
]);
|
|
|
|
assert.equal(
|
|
getTerminalPaneSnapshot({
|
|
activeTabId: "solo-1",
|
|
sessionId: "solo-1",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
"solo|solo-1",
|
|
);
|
|
assert.equal(
|
|
getTerminalPaneSnapshot({
|
|
activeTabId: "ws-split",
|
|
sessionId: "s-2",
|
|
sessionWorkspaceId: "ws-split",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
"workspace|split|ws-split",
|
|
);
|
|
assert.equal(
|
|
getTerminalPaneSnapshot({
|
|
activeTabId: "ws-focus",
|
|
sessionId: "f-1",
|
|
sessionWorkspaceId: "ws-focus",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
HIDDEN_TERMINAL_PANE_SNAPSHOT,
|
|
);
|
|
assert.equal(
|
|
getTerminalPaneSnapshot({
|
|
activeTabId: "ws-focus",
|
|
sessionId: "f-2",
|
|
sessionWorkspaceId: "ws-focus",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
"workspace|focus|ws-focus|f-2",
|
|
);
|
|
});
|
|
|
|
test("terminal pane render snapshot combines visibility and focus in one token", () => {
|
|
const workspaceById = new Map<string, Workspace>([
|
|
["ws-split", createWorkspace("ws-split", ["s-1", "s-2"], { focusedSessionId: "s-1" })],
|
|
]);
|
|
|
|
assert.equal(
|
|
getTerminalPaneRenderSnapshot({
|
|
activeTabId: "ws-split",
|
|
sessionId: "s-1",
|
|
sessionWorkspaceId: "ws-split",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
"workspace|split|ws-split|focused",
|
|
);
|
|
assert.equal(
|
|
getTerminalPaneRenderSnapshot({
|
|
activeTabId: "ws-split",
|
|
sessionId: "s-2",
|
|
sessionWorkspaceId: "ws-split",
|
|
workspaceById,
|
|
isTerminalLayerVisible: true,
|
|
}),
|
|
"workspace|split|ws-split|unfocused",
|
|
);
|
|
|
|
const parsed = parseTerminalPaneRenderSnapshot("workspace|split|ws-split|focused");
|
|
assert.equal(parsed.paneState.isVisible, true);
|
|
assert.equal(parsed.paneState.mode, "split");
|
|
assert.equal(parsed.isFocusedPane, true);
|
|
});
|