Files
Netcatty/components/terminalPaneVisibility.ts
陈大猫 36e5779d94 perf(terminal): reduce terminal tab-switch and layout jank (#1321)
* 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>
2026-06-09 03:35:03 +08:00

155 lines
4.2 KiB
TypeScript

import { activeTabStore } from "../application/state/activeTabStore";
import type { Workspace } from "../types";
export const HIDDEN_TERMINAL_PANE_SNAPSHOT = "hidden";
export type TerminalPaneSnapshot =
| typeof HIDDEN_TERMINAL_PANE_SNAPSHOT
| `solo|${string}`
| `workspace|split|${string}`
| `workspace|focus|${string}|${string}`;
export type TerminalPaneFocusSnapshot = "na" | "focused" | "unfocused";
interface GetTerminalPaneSnapshotOptions {
activeTabId: string | null;
sessionId: string;
sessionWorkspaceId?: string;
workspaceById: Map<string, Workspace>;
isTerminalLayerVisible: boolean;
}
export function getTerminalPaneSnapshot({
activeTabId,
sessionId,
sessionWorkspaceId,
workspaceById,
isTerminalLayerVisible,
}: GetTerminalPaneSnapshotOptions): TerminalPaneSnapshot {
if (!isTerminalLayerVisible || !activeTabId) {
return HIDDEN_TERMINAL_PANE_SNAPSHOT;
}
const activeWorkspace = workspaceById.get(activeTabId);
if (activeWorkspace) {
if (sessionWorkspaceId !== activeWorkspace.id) {
return HIDDEN_TERMINAL_PANE_SNAPSHOT;
}
const focusedSessionId = activeWorkspace.focusedSessionId ?? "";
if (activeWorkspace.viewMode === "focus") {
return sessionId === focusedSessionId
? `workspace|focus|${activeWorkspace.id}|${focusedSessionId}`
: HIDDEN_TERMINAL_PANE_SNAPSHOT;
}
return `workspace|split|${activeWorkspace.id}`;
}
return activeTabId === sessionId
? `solo|${sessionId}`
: HIDDEN_TERMINAL_PANE_SNAPSHOT;
}
export function parseTerminalPaneSnapshot(snapshot: TerminalPaneSnapshot): {
isVisible: boolean;
mode: "hidden" | "solo" | "split" | "focus";
workspaceId: string | null;
focusedSessionId: string | null;
} {
if (snapshot === HIDDEN_TERMINAL_PANE_SNAPSHOT) {
return {
isVisible: false,
mode: "hidden",
workspaceId: null,
focusedSessionId: null,
};
}
const parts = snapshot.split("|");
if (parts[0] === "solo") {
return {
isVisible: true,
mode: "solo",
workspaceId: null,
focusedSessionId: null,
};
}
if (parts[1] === "focus") {
return {
isVisible: true,
mode: "focus",
workspaceId: parts[2] || null,
focusedSessionId: parts[3] || null,
};
}
return {
isVisible: true,
mode: "split",
workspaceId: parts[2] || null,
focusedSessionId: null,
};
}
export function getTerminalPaneFocusSnapshot({
activeTabId: activeTabIdOverride,
sessionId,
sessionWorkspaceId,
workspaceById,
}: {
activeTabId?: string | null;
sessionId: string;
sessionWorkspaceId?: string;
workspaceById: Map<string, Workspace>;
}): TerminalPaneFocusSnapshot {
const activeTabId = activeTabIdOverride ?? activeTabStore.getActiveTabId();
if (!activeTabId) return "na";
const activeWorkspace = workspaceById.get(activeTabId);
if (!activeWorkspace || activeWorkspace.viewMode === "focus") return "na";
if (sessionWorkspaceId !== activeWorkspace.id) return "na";
return activeWorkspace.focusedSessionId === sessionId ? "focused" : "unfocused";
}
/** Combined visibility + focus snapshot for a single useSyncExternalStore subscription. */
export function getTerminalPaneRenderSnapshot(
options: GetTerminalPaneSnapshotOptions,
): string {
const pane = getTerminalPaneSnapshot(options);
if (pane === HIDDEN_TERMINAL_PANE_SNAPSHOT) {
return HIDDEN_TERMINAL_PANE_SNAPSHOT;
}
const focus = getTerminalPaneFocusSnapshot({
activeTabId: options.activeTabId,
sessionId: options.sessionId,
sessionWorkspaceId: options.sessionWorkspaceId,
workspaceById: options.workspaceById,
});
return `${pane}|${focus}`;
}
export function parseTerminalPaneRenderSnapshot(snapshot: string): {
paneState: ReturnType<typeof parseTerminalPaneSnapshot>;
isFocusedPane: boolean;
} {
if (snapshot === HIDDEN_TERMINAL_PANE_SNAPSHOT) {
return {
paneState: parseTerminalPaneSnapshot(HIDDEN_TERMINAL_PANE_SNAPSHOT),
isFocusedPane: false,
};
}
const focusSep = snapshot.lastIndexOf("|");
const focusToken = snapshot.slice(focusSep + 1);
const paneSnapshot = snapshot.slice(0, focusSep) as TerminalPaneSnapshot;
const paneState = parseTerminalPaneSnapshot(paneSnapshot);
return {
paneState,
isFocusedPane: focusToken === "focused",
};
}