705 lines
29 KiB
TypeScript
705 lines
29 KiB
TypeScript
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||
|
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
||
|
|
|
||
|
|
import { useActiveTabId } from '../../application/state/activeTabStore';
|
||
|
|
import { sessionCapabilitiesStore } from '../../application/state/sessionCapabilitiesStore';
|
||
|
|
import { useSystemManagerBackend } from '../../application/state/useSystemManagerBackend';
|
||
|
|
import { isTerminalSessionEligibleForSftpReuse } from '../../application/state/terminalConnectionReuse';
|
||
|
|
import { resolveSystemSidebarSession } from '../../domain/systemManager/resolveSystemSession';
|
||
|
|
import type { TerminalContextReader } from '../../domain/terminalContextRead';
|
||
|
|
import { useSystemCapabilitiesWarmup } from '../../application/state/useSystemManager';
|
||
|
|
import { cn } from '../../lib/utils';
|
||
|
|
import type { Host, TerminalSession, Workspace } from '../../types';
|
||
|
|
import { resolveTerminalHibernateEnabled } from '../../domain/terminalHibernate';
|
||
|
|
import { shouldMeasureTerminalLayerLayout } from '../terminalPaneVisibility';
|
||
|
|
import { TerminalLayerView } from './TerminalLayerView';
|
||
|
|
import { useTerminalAiContexts } from '../../application/state/useTerminalAiContexts';
|
||
|
|
import { useTerminalLayerEffects } from './useTerminalLayerEffects';
|
||
|
|
import { useTerminalThemePanelState } from './useTerminalThemePanelState';
|
||
|
|
import { useManualTerminalChromeSurfaceInjection } from '../../application/state/useManualTerminalChromeSurfaceInjection';
|
||
|
|
import { sidePanelLiveStore, type SidePanelLiveSnapshot } from '../../application/state/sidePanelLiveStore';
|
||
|
|
import { terminalCwdStore } from '../../application/state/terminalCwdStore';
|
||
|
|
import { useTerminalWorkspaceLayout } from './useTerminalWorkspaceLayout';
|
||
|
|
import type { SidePanelTab } from './TerminalLayerSupport';
|
||
|
|
import {
|
||
|
|
collectSidePanelPanes,
|
||
|
|
type SidePanelLayout,
|
||
|
|
} from '../../domain/sidePanelLayout';
|
||
|
|
|
||
|
|
type StableRef = React.MutableRefObject<Record<string, any>>;
|
||
|
|
|
||
|
|
export function TerminalLayerTabBridge({ stableRef }: { stableRef: StableRef }) {
|
||
|
|
const s = stableRef.current;
|
||
|
|
const activeTabId = useActiveTabId();
|
||
|
|
// Subscribe to cwd store so OSC 7 updates reach the side-panel live snapshot
|
||
|
|
// without TerminalLayerInner setState.
|
||
|
|
const terminalCwdVersion = useSyncExternalStore(
|
||
|
|
terminalCwdStore.subscribe,
|
||
|
|
terminalCwdStore.getVersion,
|
||
|
|
terminalCwdStore.getVersion,
|
||
|
|
);
|
||
|
|
const systemBackend = useSystemManagerBackend();
|
||
|
|
const terminalContextReadersRef = useRef<Map<string, TerminalContextReader>>(new Map());
|
||
|
|
|
||
|
|
s.activeTabIdRef.current = activeTabId;
|
||
|
|
|
||
|
|
const workspaceById = s.workspaceById as Map<string, Workspace>;
|
||
|
|
const sessions = s.sessions as TerminalSession[];
|
||
|
|
const sessionHostsMap = s.sessionHostsMap as Map<string, Host>;
|
||
|
|
const sftpHostForTab = s.sftpHostForTab as Map<string, Host>;
|
||
|
|
const sidePanelOpenTabs = s.sidePanelOpenTabs as Map<string, SidePanelTab>;
|
||
|
|
const sidePanelLayouts = s.sidePanelLayouts as Map<string, SidePanelLayout>;
|
||
|
|
const showHostTreeSidebar = s.showHostTreeSidebar as boolean | undefined;
|
||
|
|
|
||
|
|
const activeWorkspace = useMemo(
|
||
|
|
() => (activeTabId ? workspaceById.get(activeTabId) : undefined),
|
||
|
|
[activeTabId, workspaceById],
|
||
|
|
);
|
||
|
|
const activeSession = useMemo(
|
||
|
|
() => sessions.find((session) => session.id === activeTabId),
|
||
|
|
[activeTabId, sessions],
|
||
|
|
);
|
||
|
|
const isFocusMode = activeWorkspace?.viewMode === 'focus';
|
||
|
|
const focusedSessionId = activeWorkspace?.focusedSessionId;
|
||
|
|
const hibernateHiddenTabs = resolveTerminalHibernateEnabled(s.terminalSettings);
|
||
|
|
const localWorkspaceIds = useMemo(() => new Set(
|
||
|
|
sessions
|
||
|
|
.filter((session) => session.workspaceId && sessionHostsMap.get(session.id)?.protocol === 'local')
|
||
|
|
.map((session) => session.workspaceId as string),
|
||
|
|
), [sessionHostsMap, sessions]);
|
||
|
|
const shouldKeepHiddenWorkspaceLaidOut = useCallback(
|
||
|
|
(workspace: Workspace) => !hibernateHiddenTabs || localWorkspaceIds.has(workspace.id),
|
||
|
|
[hibernateHiddenTabs, localWorkspaceIds],
|
||
|
|
);
|
||
|
|
const keepHiddenLayoutActive = !hibernateHiddenTabs || localWorkspaceIds.size > 0;
|
||
|
|
const effectiveFocusedSessionId = useMemo((): string | null => {
|
||
|
|
if (activeWorkspace) {
|
||
|
|
if (focusedSessionId) return focusedSessionId;
|
||
|
|
return sessions.find((session) => session.workspaceId === activeWorkspace.id)?.id ?? null;
|
||
|
|
}
|
||
|
|
return activeSession?.id ?? null;
|
||
|
|
}, [activeSession?.id, activeWorkspace, focusedSessionId, sessions]);
|
||
|
|
|
||
|
|
s.activeWorkspaceRef.current = activeWorkspace;
|
||
|
|
s.activeSessionRef.current = activeSession;
|
||
|
|
s.focusedSessionIdRef.current = focusedSessionId;
|
||
|
|
|
||
|
|
const isVisible = Boolean(activeSession || activeWorkspace || s.draggingSessionId);
|
||
|
|
const isTerminalLayerVisible = isVisible || !!s.draggingSessionId;
|
||
|
|
|
||
|
|
const {
|
||
|
|
activeResizers,
|
||
|
|
computeSplitHint,
|
||
|
|
dropHint,
|
||
|
|
findSplitNode,
|
||
|
|
handleWorkspaceDrop,
|
||
|
|
resizing,
|
||
|
|
setDropHint,
|
||
|
|
setResizing,
|
||
|
|
setWorkspaceArea,
|
||
|
|
workspaceArea,
|
||
|
|
workspaceInnerRef,
|
||
|
|
workspaceOuterRef,
|
||
|
|
workspaceOverlayRef,
|
||
|
|
workspaceRectsById,
|
||
|
|
} = useTerminalWorkspaceLayout({
|
||
|
|
activeSession,
|
||
|
|
activeWorkspace,
|
||
|
|
isFocusMode,
|
||
|
|
shouldKeepHiddenWorkspaceLaidOut,
|
||
|
|
onAddSessionToWorkspace: s.onAddSessionToWorkspace,
|
||
|
|
onCreateWorkspaceFromSessions: s.onCreateWorkspaceFromSessions,
|
||
|
|
onSetDraggingSessionId: s.onSetDraggingSessionId,
|
||
|
|
onUpdateSplitSizes: s.onUpdateSplitSizes,
|
||
|
|
sessions,
|
||
|
|
workspaces: s.workspaces,
|
||
|
|
});
|
||
|
|
|
||
|
|
const isSidePanelOpenForCurrentTab = activeTabId ? sidePanelOpenTabs.has(activeTabId) : false;
|
||
|
|
const activeSidePanelTab = activeTabId ? sidePanelOpenTabs.get(activeTabId) ?? null : null;
|
||
|
|
const activeSidePanelLayout = activeTabId ? sidePanelLayouts.get(activeTabId) ?? null : null;
|
||
|
|
const activeSidePanelTools = useMemo(
|
||
|
|
() => new Set(activeSidePanelLayout ? collectSidePanelPanes(activeSidePanelLayout.root).map((pane) => pane.tool) : []),
|
||
|
|
[activeSidePanelLayout],
|
||
|
|
);
|
||
|
|
const isSftpOpenForCurrentTab = activeSidePanelTools.has('sftp');
|
||
|
|
|
||
|
|
const activeHostIdForSidebar = useMemo(() => {
|
||
|
|
const sessionId = activeWorkspace ? focusedSessionId : activeSession?.id;
|
||
|
|
if (!sessionId) return null;
|
||
|
|
return sessionHostsMap.get(sessionId)?.id
|
||
|
|
?? sessions.find((session) => session.id === sessionId)?.hostId
|
||
|
|
?? null;
|
||
|
|
}, [activeWorkspace, focusedSessionId, activeSession, sessionHostsMap, sessions]);
|
||
|
|
|
||
|
|
const sftpActiveHost = useMemo((): Host | null => {
|
||
|
|
if (!isSftpOpenForCurrentTab || !activeTabId) return null;
|
||
|
|
if (activeWorkspace && focusedSessionId) {
|
||
|
|
return sessionHostsMap.get(focusedSessionId) ?? sftpHostForTab.get(activeTabId) ?? null;
|
||
|
|
}
|
||
|
|
if (activeSession) {
|
||
|
|
return sessionHostsMap.get(activeSession.id) ?? sftpHostForTab.get(activeTabId) ?? null;
|
||
|
|
}
|
||
|
|
return sftpHostForTab.get(activeTabId) ?? null;
|
||
|
|
}, [activeSession, activeTabId, activeWorkspace, focusedSessionId, isSftpOpenForCurrentTab, sessionHostsMap, sftpHostForTab]);
|
||
|
|
|
||
|
|
// Keep the same-endpoint SSH session id across disconnected/connecting so
|
||
|
|
// SftpSidePanel can observe status transitions and rebind after Start over.
|
||
|
|
// Transport reuse for openSftp still requires status === "connected".
|
||
|
|
const activeTerminalSessionIdForSftp = useMemo((): string | null => {
|
||
|
|
if (!isSftpOpenForCurrentTab || !sftpActiveHost) return null;
|
||
|
|
const sessionId = activeWorkspace ? focusedSessionId : activeSession?.id;
|
||
|
|
if (!sessionId) return null;
|
||
|
|
const session = sessions.find((candidate) => candidate.id === sessionId);
|
||
|
|
if (!session || !isTerminalSessionEligibleForSftpReuse(session)) return null;
|
||
|
|
const sessionHost = sessionHostsMap.get(session.id);
|
||
|
|
if (!sessionHost) return null;
|
||
|
|
const sameEndpoint =
|
||
|
|
sessionHost.hostname === sftpActiveHost.hostname
|
||
|
|
&& (sessionHost.port || 22) === (sftpActiveHost.port || 22)
|
||
|
|
&& (sessionHost.username || 'root') === (sftpActiveHost.username || 'root');
|
||
|
|
return sameEndpoint ? session.id : null;
|
||
|
|
}, [activeSession?.id, activeWorkspace, focusedSessionId, isSftpOpenForCurrentTab, sessions, sessionHostsMap, sftpActiveHost]);
|
||
|
|
|
||
|
|
const linkedTerminalSessionIdForSftp = useMemo((): string | null => {
|
||
|
|
if (!isSftpOpenForCurrentTab) return null;
|
||
|
|
if (activeTerminalSessionIdForSftp) return activeTerminalSessionIdForSftp;
|
||
|
|
return activeWorkspace ? (focusedSessionId ?? null) : (activeSession?.id ?? null);
|
||
|
|
}, [
|
||
|
|
activeSession?.id,
|
||
|
|
activeTerminalSessionIdForSftp,
|
||
|
|
activeWorkspace,
|
||
|
|
focusedSessionId,
|
||
|
|
isSftpOpenForCurrentTab,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Recomputed when terminalCwdVersion changes (useSyncExternalStore above).
|
||
|
|
const activeTerminalCwd = linkedTerminalSessionIdForSftp
|
||
|
|
? (
|
||
|
|
terminalCwdStore.getCwd(linkedTerminalSessionIdForSftp)
|
||
|
|
?? s.terminalRendererCwdBySessionRef.current.get(linkedTerminalSessionIdForSftp)
|
||
|
|
?? null
|
||
|
|
)
|
||
|
|
: null;
|
||
|
|
const activeTerminalCwdSource = linkedTerminalSessionIdForSftp
|
||
|
|
? (
|
||
|
|
terminalCwdStore.getSource(linkedTerminalSessionIdForSftp)
|
||
|
|
?? s.terminalRendererCwdSourceBySessionRef.current.get(linkedTerminalSessionIdForSftp)
|
||
|
|
)
|
||
|
|
: undefined;
|
||
|
|
const activeTerminalCwdTrusted = activeTerminalCwdSource === 'osc7'
|
||
|
|
|| activeTerminalCwdSource === 'backend-strict';
|
||
|
|
void terminalCwdVersion;
|
||
|
|
|
||
|
|
const historySessionId = effectiveFocusedSessionId;
|
||
|
|
const activeTerminalSessionForSystem = useMemo(
|
||
|
|
() => resolveSystemSidebarSession(sessions, activeWorkspace, focusedSessionId, activeSession),
|
||
|
|
[activeSession, activeWorkspace, focusedSessionId, sessions],
|
||
|
|
);
|
||
|
|
const activeSystemSessionHost = useMemo((): Host | null => {
|
||
|
|
const id = activeTerminalSessionForSystem?.id;
|
||
|
|
if (!id) return null;
|
||
|
|
return sessionHostsMap.get(id) ?? null;
|
||
|
|
}, [activeTerminalSessionForSystem?.id, sessionHostsMap]);
|
||
|
|
|
||
|
|
const systemWarmupSessionIds = useMemo(() => {
|
||
|
|
if (!activeTabId || !activeSidePanelTools.has('system')) return [];
|
||
|
|
const session = activeTerminalSessionForSystem;
|
||
|
|
if (!session || session.status !== 'connected') return [];
|
||
|
|
return [session.id];
|
||
|
|
}, [activeSidePanelTools, activeTabId, activeTerminalSessionForSystem]);
|
||
|
|
|
||
|
|
useSystemCapabilitiesWarmup(
|
||
|
|
systemWarmupSessionIds,
|
||
|
|
systemBackend,
|
||
|
|
systemWarmupSessionIds.length > 0,
|
||
|
|
(s.terminalSettings?.systemManagerProcessRefreshInterval ?? 3) * 1000,
|
||
|
|
);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
sessionCapabilitiesStore.prune(new Set(sessions.map((session) => session.id)));
|
||
|
|
}, [sessions]);
|
||
|
|
|
||
|
|
const focusedHost = useMemo((): Host | null => {
|
||
|
|
if (!historySessionId) return null;
|
||
|
|
return sessionHostsMap.get(historySessionId) ?? null;
|
||
|
|
}, [historySessionId, sessionHostsMap]);
|
||
|
|
|
||
|
|
const themeState = useTerminalThemePanelState({
|
||
|
|
activeSession,
|
||
|
|
activeSidePanelTab: activeSidePanelTools.has('theme') ? 'theme' : activeSidePanelTab,
|
||
|
|
activeWorkspace,
|
||
|
|
followAppTerminalTheme: s.followAppTerminalTheme,
|
||
|
|
focusedSessionId,
|
||
|
|
fontSize: s.fontSize,
|
||
|
|
hostMap: s.hostMap,
|
||
|
|
isSidePanelOpenForCurrentTab,
|
||
|
|
isVisible,
|
||
|
|
onUpdateHost: s.onUpdateHost,
|
||
|
|
onUpdateTerminalFontFamilyId: s.onUpdateTerminalFontFamilyId,
|
||
|
|
onUpdateTerminalFontSize: s.onUpdateTerminalFontSize,
|
||
|
|
onUpdateSessionFontSize: s.onUpdateSessionFontSize,
|
||
|
|
onClearSessionFontSizeOverride: s.onClearSessionFontSizeOverride,
|
||
|
|
onUpdateTerminalFontWeight: s.onUpdateTerminalFontWeight,
|
||
|
|
onUpdateTerminalThemeId: s.onUpdateTerminalThemeId,
|
||
|
|
pickTheme: s.pickTerminalTheme,
|
||
|
|
clearIntent: s.clearThemeIntent,
|
||
|
|
resolveFocusedAppearance: s.resolveSessionAppearance,
|
||
|
|
sessionHostsMap,
|
||
|
|
terminalFontFamilyId: s.terminalFontFamilyId,
|
||
|
|
terminalSettings: s.terminalSettings,
|
||
|
|
terminalTheme: s.terminalTheme,
|
||
|
|
});
|
||
|
|
|
||
|
|
useManualTerminalChromeSurfaceInjection(
|
||
|
|
themeState.resolvedPreviewTheme,
|
||
|
|
!s.followAppTerminalTheme && isTerminalLayerVisible,
|
||
|
|
);
|
||
|
|
|
||
|
|
const sidePanelLiveSnapshot = useMemo<SidePanelLiveSnapshot>(() => ({
|
||
|
|
sftpActiveHost,
|
||
|
|
activeTerminalSessionIdForSftp,
|
||
|
|
activeTerminalCwd,
|
||
|
|
activeTerminalCwdTrusted,
|
||
|
|
activeWorkspace,
|
||
|
|
activeTerminalSessionForSystem: activeTerminalSessionForSystem ?? null,
|
||
|
|
activeSystemSessionHost,
|
||
|
|
focusedHost,
|
||
|
|
focusedSessionId: effectiveFocusedSessionId,
|
||
|
|
historySessionId,
|
||
|
|
resolvedPreviewTheme: themeState.resolvedPreviewTheme,
|
||
|
|
previewedOrVisibleThemeId: themeState.previewedOrVisibleThemeId,
|
||
|
|
focusedFontFamilyId: themeState.focusedFontFamilyId,
|
||
|
|
focusedFontFamilyOverridden: themeState.focusedFontFamilyOverridden,
|
||
|
|
focusedFontSize: themeState.focusedFontSize,
|
||
|
|
focusedFontSizeOverridden: themeState.focusedFontSizeOverridden,
|
||
|
|
focusedFontWeight: themeState.focusedFontWeight,
|
||
|
|
focusedFontWeightOverridden: themeState.focusedFontWeightOverridden,
|
||
|
|
focusedThemeOverridden: themeState.focusedThemeOverridden,
|
||
|
|
}), [
|
||
|
|
activeSystemSessionHost,
|
||
|
|
activeTerminalCwd,
|
||
|
|
activeTerminalCwdTrusted,
|
||
|
|
activeTerminalSessionForSystem,
|
||
|
|
activeTerminalSessionIdForSftp,
|
||
|
|
activeWorkspace,
|
||
|
|
effectiveFocusedSessionId,
|
||
|
|
focusedHost,
|
||
|
|
historySessionId,
|
||
|
|
sftpActiveHost,
|
||
|
|
themeState.focusedFontFamilyId,
|
||
|
|
themeState.focusedFontFamilyOverridden,
|
||
|
|
themeState.focusedFontSize,
|
||
|
|
themeState.focusedFontSizeOverridden,
|
||
|
|
themeState.focusedFontWeight,
|
||
|
|
themeState.focusedFontWeightOverridden,
|
||
|
|
themeState.focusedThemeOverridden,
|
||
|
|
themeState.previewedOrVisibleThemeId,
|
||
|
|
themeState.resolvedPreviewTheme,
|
||
|
|
]);
|
||
|
|
|
||
|
|
useLayoutEffect(() => {
|
||
|
|
sidePanelLiveStore.update(sidePanelLiveSnapshot);
|
||
|
|
}, [sidePanelLiveSnapshot]);
|
||
|
|
|
||
|
|
const { aiContextsByTabId, resolveAIExecutorContext } = useTerminalAiContexts({
|
||
|
|
hosts: s.hosts,
|
||
|
|
hostsRef: s.hostsRef,
|
||
|
|
portForwardingRules: s.portForwardingRules,
|
||
|
|
portForwardingRulesRef: s.portForwardingRulesRef,
|
||
|
|
mountedAiTabIds: s.mountedAiTabIds,
|
||
|
|
sessionHostsMap,
|
||
|
|
sessions,
|
||
|
|
sessionsRef: s.sessionsRef,
|
||
|
|
terminalContextReadersRef,
|
||
|
|
workspaces: s.workspaces,
|
||
|
|
workspacesRef: s.workspacesRef,
|
||
|
|
});
|
||
|
|
|
||
|
|
const handleTerminalContextReaderChange = React.useCallback((
|
||
|
|
sessionId: string,
|
||
|
|
reader: TerminalContextReader | null,
|
||
|
|
) => {
|
||
|
|
if (reader) {
|
||
|
|
terminalContextReadersRef.current.set(sessionId, reader);
|
||
|
|
} else {
|
||
|
|
terminalContextReadersRef.current.delete(sessionId);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const prevFocusedSessionIdRef = useRef<string | undefined>(undefined);
|
||
|
|
|
||
|
|
useTerminalLayerEffects({
|
||
|
|
activeSidePanelTab,
|
||
|
|
activeSidePanelLayout,
|
||
|
|
activeTabId,
|
||
|
|
activeTabIdRef: s.activeTabIdRef,
|
||
|
|
activeWorkspace,
|
||
|
|
activityTrackedSessions: s.activityTrackedSessions,
|
||
|
|
cancelAnimationFrame,
|
||
|
|
ChunkedEscapeFilter: s.ChunkedEscapeFilter,
|
||
|
|
clearTimeout,
|
||
|
|
document,
|
||
|
|
dropHint,
|
||
|
|
effectiveHosts: s.effectiveHosts,
|
||
|
|
filterTabsMap: s.filterTabsMap,
|
||
|
|
onConnectToHost: s.onConnectToHost,
|
||
|
|
focusedSessionId,
|
||
|
|
getSessionActivityIdsToClear: s.getSessionActivityIdsToClear,
|
||
|
|
handleToggleAiFromTopBar: s.handleToggleAiFromTopBar,
|
||
|
|
handleToggleSystemFromTopBar: s.handleToggleSystemFromTopBar,
|
||
|
|
handleToggleScriptsSidePanel: s.handleToggleScriptsSidePanel,
|
||
|
|
handleToggleSidePanel: s.handleToggleSidePanel,
|
||
|
|
hasNotifiableTerminalOutput: s.hasNotifiableTerminalOutput,
|
||
|
|
isComposeBarOpen: s.isComposeBarOpen,
|
||
|
|
isFocusMode,
|
||
|
|
isTerminalLayerVisible,
|
||
|
|
shouldMeasureTerminalLayerLayout: shouldMeasureTerminalLayerLayout({
|
||
|
|
isTerminalLayerVisible,
|
||
|
|
keepHiddenLayoutActive,
|
||
|
|
workspaceArea,
|
||
|
|
}),
|
||
|
|
lastSidePanelTabRef: s.lastSidePanelTabRef,
|
||
|
|
Map,
|
||
|
|
Math,
|
||
|
|
onSessionData: s.onSessionData,
|
||
|
|
onSplitSessionRef: s.onSplitSessionRef,
|
||
|
|
onToggleBroadcastRef: s.onToggleBroadcastRef,
|
||
|
|
onToggleWorkspaceViewModeRef: s.onToggleWorkspaceViewModeRef,
|
||
|
|
prevFocusedSessionIdRef,
|
||
|
|
refocusActiveTerminalSession: s.refocusActiveTerminalSession,
|
||
|
|
requestAnimationFrame,
|
||
|
|
ResizeObserver,
|
||
|
|
sessionActivityStore: s.sessionActivityStore,
|
||
|
|
sessionHostsMap,
|
||
|
|
sessions,
|
||
|
|
Set,
|
||
|
|
setDropHint,
|
||
|
|
setSftpHostForTab: s.setSftpHostForTab,
|
||
|
|
setSftpInitialLocationForTab: s.setSftpInitialLocationForTab,
|
||
|
|
setSftpPendingUploadsForTab: s.setSftpPendingUploadsForTab,
|
||
|
|
setAiMountedTabIds: s.setAiMountedTabIds,
|
||
|
|
setNotesMountedTabIds: s.setNotesMountedTabIds,
|
||
|
|
setScriptsMountedTabIds: s.setScriptsMountedTabIds,
|
||
|
|
setSystemMountedTabIds: s.setSystemMountedTabIds,
|
||
|
|
setThemeMountedTabIds: s.setThemeMountedTabIds,
|
||
|
|
setSidePanelOpenTabs: s.setSidePanelOpenTabs,
|
||
|
|
setSidePanelLayouts: s.setSidePanelLayouts,
|
||
|
|
setTimeout,
|
||
|
|
setWorkspaceArea,
|
||
|
|
sidePanelPosition: s.sidePanelPosition,
|
||
|
|
sidePanelWidth: s.sidePanelWidth,
|
||
|
|
sftpActiveHost,
|
||
|
|
sftpHostForTab,
|
||
|
|
sftpPaneClosedTabIdsRef: s.sftpPaneClosedTabIdsRef,
|
||
|
|
shouldMarkSessionActivity: s.shouldMarkSessionActivity,
|
||
|
|
sidePanelOpenTabs,
|
||
|
|
splitHorizontalHandlersRef: s.splitHorizontalHandlersRef,
|
||
|
|
splitVerticalHandlersRef: s.splitVerticalHandlersRef,
|
||
|
|
terminalRendererCwdBySessionRef: s.terminalRendererCwdBySessionRef,
|
||
|
|
toggleScriptsSidePanelRef: s.toggleScriptsSidePanelRef,
|
||
|
|
toggleSidePanelRef: s.toggleSidePanelRef,
|
||
|
|
validAIScopeTargetIds: s.validAIScopeTargetIds,
|
||
|
|
validSessionActivityIds: s.validSessionActivityIds,
|
||
|
|
window,
|
||
|
|
workspaceBroadcastHandlersRef: s.workspaceBroadcastHandlersRef,
|
||
|
|
workspaceFocusHandlersRef: s.workspaceFocusHandlersRef,
|
||
|
|
workspaceInnerRef,
|
||
|
|
workspaces: s.workspaces,
|
||
|
|
});
|
||
|
|
|
||
|
|
const ctx = useMemo(() => ({
|
||
|
|
accentMode: s.accentMode,
|
||
|
|
activeHostIdForSidebar,
|
||
|
|
activeResizers,
|
||
|
|
activeSidePanelTab,
|
||
|
|
activeSidePanelLayout,
|
||
|
|
activeTabId,
|
||
|
|
activeTerminalCwd,
|
||
|
|
activeTerminalCwdTrusted,
|
||
|
|
activeTerminalSessionIdForSftp,
|
||
|
|
activeWorkspace,
|
||
|
|
AIChatPanelsHost: s.AIChatPanelsHost,
|
||
|
|
AISidePanelStateRoot: s.AISidePanelStateRoot,
|
||
|
|
aiContextsByTabId,
|
||
|
|
Array: s.Array,
|
||
|
|
Button: s.Button,
|
||
|
|
cn,
|
||
|
|
composeBarThemeColors: themeState.composeBarThemeColors,
|
||
|
|
computeSplitHint,
|
||
|
|
customAccent: s.customAccent,
|
||
|
|
customGroups: s.customGroups,
|
||
|
|
draggingSessionId: s.draggingSessionId,
|
||
|
|
dropHint,
|
||
|
|
editorWordWrap: s.editorWordWrap,
|
||
|
|
effectiveHosts: s.effectiveHosts,
|
||
|
|
findSplitNode,
|
||
|
|
focusedFontFamilyId: themeState.focusedFontFamilyId,
|
||
|
|
focusedFontFamilyOverridden: themeState.focusedFontFamilyOverridden,
|
||
|
|
focusedFontSize: themeState.focusedFontSize,
|
||
|
|
focusedFontSizeOverridden: themeState.focusedFontSizeOverridden,
|
||
|
|
focusedFontWeight: themeState.focusedFontWeight,
|
||
|
|
focusedFontWeightOverridden: themeState.focusedFontWeightOverridden,
|
||
|
|
focusedHost,
|
||
|
|
focusedSessionId,
|
||
|
|
focusedThemeOverridden: themeState.focusedThemeOverridden,
|
||
|
|
FolderTree: s.FolderTree,
|
||
|
|
followAppTerminalTheme: s.followAppTerminalTheme,
|
||
|
|
handleHistoryPaste: s.handleHistoryPaste,
|
||
|
|
handleHistoryDelete: s.handleHistoryDelete,
|
||
|
|
handleHistoryRun: s.handleHistoryRun,
|
||
|
|
handleFocusSidePanelPane: s.handleFocusSidePanelPane,
|
||
|
|
handleMagnifySidePanelPane: s.handleMagnifySidePanelPane,
|
||
|
|
handleRestoreMagnifiedPane: s.handleRestoreMagnifiedPane,
|
||
|
|
handleMagnifyTerminalPane: s.handleMagnifyTerminalPane,
|
||
|
|
handleTerminalPaneInteraction: s.handleTerminalPaneInteraction,
|
||
|
|
handleSplitSidePanelPane: s.handleSplitSidePanelPane,
|
||
|
|
handleCloseSidePanelPane: s.handleCloseSidePanelPane,
|
||
|
|
handleResizeSidePanelSplit: s.handleResizeSidePanelSplit,
|
||
|
|
fontSize: s.fontSize,
|
||
|
|
getTerminalCwd: s.getTerminalCwd,
|
||
|
|
handleAddKnownHost: s.handleAddKnownHost,
|
||
|
|
handleAddSelectionToAI: s.handleAddSelectionToAI,
|
||
|
|
handleBroadcastInput: s.handleBroadcastInput,
|
||
|
|
handleCloseSession: s.handleCloseSession,
|
||
|
|
handleCloseSidePanel: s.handleCloseSidePanel,
|
||
|
|
handleCommandExecuted: s.handleCommandExecuted,
|
||
|
|
handleCommandSubmitted: s.handleCommandSubmitted,
|
||
|
|
handleComposeSend: s.handleComposeSend,
|
||
|
|
handleFontFamilyChangeForFocusedSession: themeState.handleFontFamilyChangeForFocusedSession,
|
||
|
|
handleFontFamilyResetForFocusedSession: themeState.handleFontFamilyResetForFocusedSession,
|
||
|
|
handleFontSizeChangeForFocusedSession: themeState.handleFontSizeChangeForFocusedSession,
|
||
|
|
handleFontSizeResetForFocusedSession: themeState.handleFontSizeResetForFocusedSession,
|
||
|
|
handleFontWeightChangeForFocusedSession: themeState.handleFontWeightChangeForFocusedSession,
|
||
|
|
handleFontWeightResetForFocusedSession: themeState.handleFontWeightResetForFocusedSession,
|
||
|
|
handleOpenAI: s.handleOpenAI,
|
||
|
|
handleOpenNotes: s.handleOpenNotes,
|
||
|
|
handleOpenSystem: s.handleOpenSystem,
|
||
|
|
handleOpenHistory: s.handleOpenHistory,
|
||
|
|
handleOpenScripts: s.handleOpenScripts,
|
||
|
|
activeTerminalSessionForSystem,
|
||
|
|
activeSystemSessionHost,
|
||
|
|
handleOpenSftp: s.handleOpenSftp,
|
||
|
|
handleOpenTheme: s.handleOpenTheme,
|
||
|
|
handleBackFromNotes: s.handleBackFromNotes,
|
||
|
|
handleOpenHostFromNotes: s.handleOpenHostFromNotes,
|
||
|
|
History: s.History,
|
||
|
|
historySessionId,
|
||
|
|
HistorySidePanel: s.HistorySidePanel,
|
||
|
|
// remoteHistory / shellHistory are owned by History side-panel slot stores
|
||
|
|
hibernateHiddenTabs,
|
||
|
|
handleOsDetected: s.handleOsDetected,
|
||
|
|
handlePendingTerminalSelectionConsumed: s.handlePendingTerminalSelectionConsumed,
|
||
|
|
handlePendingUploadHandled: s.handlePendingUploadHandled,
|
||
|
|
handleSessionExit: s.handleSessionExit,
|
||
|
|
handleSftpCurrentPathChange: s.handleSftpCurrentPathChange,
|
||
|
|
handleSftpActiveTransfersChange: s.handleSftpActiveTransfersChange,
|
||
|
|
handleSftpActiveExternalEditsChange: s.handleSftpActiveExternalEditsChange,
|
||
|
|
handleSftpInitialLocationApplied: s.handleSftpInitialLocationApplied,
|
||
|
|
persistSidePanelWidth: s.persistSidePanelWidth,
|
||
|
|
setSidePanelWidth: s.setSidePanelWidth,
|
||
|
|
handleSnippetClickForFocusedSession: s.handleSnippetClickForFocusedSession,
|
||
|
|
handleSnippetFromPanel: s.handleSnippetFromPanel,
|
||
|
|
handleRunScriptFromPanel: s.handleRunScriptFromPanel,
|
||
|
|
handleRunScriptOnWorkspace: s.handleRunScriptOnWorkspace,
|
||
|
|
handleStartRecordingFromPanel: s.handleStartRecordingFromPanel,
|
||
|
|
handleStopScriptRun: s.handleStopScriptRun,
|
||
|
|
handlePauseScriptRun: s.handlePauseScriptRun,
|
||
|
|
handleResumeScriptRun: s.handleResumeScriptRun,
|
||
|
|
handleSnippetExecutorChange: s.handleSnippetExecutorChange,
|
||
|
|
handleBroadcastInterruptPriorityChange: s.handleBroadcastInterruptPriorityChange,
|
||
|
|
handleProgrammaticCommandLogRewriteChange: s.handleProgrammaticCommandLogRewriteChange,
|
||
|
|
handleStatusChange: s.handleStatusChange,
|
||
|
|
handleTerminalCwdChange: s.handleTerminalCwdChange,
|
||
|
|
handleTerminalTitleChange: s.handleTerminalTitleChange,
|
||
|
|
handleTerminalBell: s.handleTerminalBell,
|
||
|
|
handleTerminalOutput: s.handleTerminalOutput,
|
||
|
|
handleTerminalDataCapture: s.handleTerminalDataCapture,
|
||
|
|
handleTerminalContextReaderChange,
|
||
|
|
handleTerminalFontSizeChange: s.handleTerminalFontSizeChange,
|
||
|
|
handleThemeChangeForFocusedSession: themeState.handleThemeChangeForFocusedSession,
|
||
|
|
handleThemeResetForFocusedSession: themeState.handleThemeResetForFocusedSession,
|
||
|
|
handleToggleSftpFromBar: s.handleToggleSftpFromBar,
|
||
|
|
handleToggleWorkspaceComposeBar: s.handleToggleWorkspaceComposeBar,
|
||
|
|
handleUpdateHost: s.handleUpdateHost,
|
||
|
|
handleWorkspaceDrop,
|
||
|
|
hosts: s.hosts,
|
||
|
|
hotkeyScheme: s.hotkeyScheme,
|
||
|
|
disableTerminalFontZoom: s.disableTerminalFontZoom,
|
||
|
|
restoreTerminalCwd: s.restoreTerminalCwd,
|
||
|
|
identities: s.identities,
|
||
|
|
isBroadcastEnabled: s.isBroadcastEnabled,
|
||
|
|
isComposeBarOpen: s.isComposeBarOpen,
|
||
|
|
isFocusMode,
|
||
|
|
isSidePanelOpenForCurrentTab,
|
||
|
|
isTerminalLayerVisible,
|
||
|
|
keyBindings: s.keyBindings,
|
||
|
|
keys: s.keys,
|
||
|
|
knownHosts: s.knownHosts,
|
||
|
|
MessageSquare: s.MessageSquare,
|
||
|
|
magnifiedPane: s.magnifiedPane,
|
||
|
|
mountedAiTabIds: s.mountedAiTabIds,
|
||
|
|
mountedSftpTabIds: s.mountedSftpTabIds,
|
||
|
|
notesMountedTabIds: s.notesMountedTabIds,
|
||
|
|
notesOpenNoteByTab: s.notesOpenNoteByTab,
|
||
|
|
NotesManager: s.NotesManager,
|
||
|
|
scriptsMountedTabIds: s.scriptsMountedTabIds,
|
||
|
|
systemMountedTabIds: s.systemMountedTabIds,
|
||
|
|
themeMountedTabIds: s.themeMountedTabIds,
|
||
|
|
onConnectToHost: s.onConnectToHost,
|
||
|
|
onCreateLocalTerminal: s.onCreateLocalTerminal,
|
||
|
|
onHotkeyAction: s.onHotkeyAction,
|
||
|
|
onReorderWorkspaceSessions: s.onReorderWorkspaceSessions,
|
||
|
|
onReorderTabs: s.onReorderTabs,
|
||
|
|
onCopySession: s.onCopySession,
|
||
|
|
onDuplicateSession: s.onDuplicateSession,
|
||
|
|
onCopySessionToNewWindow: s.onCopySessionToNewWindow,
|
||
|
|
onUpdateSessionRestoreCwd: s.onUpdateSessionRestoreCwd,
|
||
|
|
onUpdateSessionDynamicTitle: s.onUpdateSessionDynamicTitle,
|
||
|
|
onUpdateSessionCodingCliProvider: s.onUpdateSessionCodingCliProvider,
|
||
|
|
onRequestAddToWorkspace: s.onRequestAddToWorkspace,
|
||
|
|
onAppendHostToWorkspace: s.onAppendHostToWorkspace,
|
||
|
|
onSetWorkspaceFocusedSession: s.onSetWorkspaceFocusedSession,
|
||
|
|
onStartSessionRename: s.onStartSessionRename,
|
||
|
|
onSubmitSessionRename: s.onSubmitSessionRename,
|
||
|
|
onRemoveSessionFromWorkspace: s.onRemoveSessionFromWorkspace,
|
||
|
|
onOpenVaultNoteFromChat: s.onOpenVaultNoteFromChat,
|
||
|
|
onOpenVaultHostFromChat: s.onOpenVaultHostFromChat,
|
||
|
|
onOpenVaultSectionFromChat: s.onOpenVaultSectionFromChat,
|
||
|
|
onOpenVaultSnippetFromChat: s.onOpenVaultSnippetFromChat,
|
||
|
|
onStartSessionDrag: s.onStartSessionDrag,
|
||
|
|
onEndSessionDrag: s.onEndSessionDrag,
|
||
|
|
onSplitSession: s.onSplitSession,
|
||
|
|
onToggleWorkspaceViewMode: s.onToggleWorkspaceViewMode,
|
||
|
|
Palette: s.Palette,
|
||
|
|
PanelLeft: s.PanelLeft,
|
||
|
|
PanelRight: s.PanelRight,
|
||
|
|
pendingTerminalSelectionForAI: s.pendingTerminalSelectionForAI,
|
||
|
|
previewedOrVisibleThemeId: themeState.previewedOrVisibleThemeId,
|
||
|
|
refocusActiveTerminalSession: s.refocusActiveTerminalSession,
|
||
|
|
refocusTerminalSession: s.refocusTerminalSession,
|
||
|
|
resizing,
|
||
|
|
resolveAIExecutorContext,
|
||
|
|
resolvedPreviewTheme: themeState.resolvedPreviewTheme,
|
||
|
|
ScriptsSidePanel: s.ScriptsSidePanel,
|
||
|
|
sessionChainHostsMap: s.sessionChainHostsMap,
|
||
|
|
sessionHostsMap,
|
||
|
|
resolvedSessionHostIds: s.resolvedSessionHostIds,
|
||
|
|
sessionLogConfig: s.sessionLogConfig,
|
||
|
|
sessionSudoAutofillPasswordsMap: s.sessionSudoAutofillPasswordsMap,
|
||
|
|
sessionSudoAutofillCandidatesMap: s.sessionSudoAutofillCandidatesMap,
|
||
|
|
sessions,
|
||
|
|
setDropHint,
|
||
|
|
setEditorWordWrap: s.setEditorWordWrap,
|
||
|
|
setIsComposeBarOpen: s.setIsComposeBarOpen,
|
||
|
|
setResizing,
|
||
|
|
showHostTreeSidebar,
|
||
|
|
setSidePanelPosition: s.setSidePanelPosition,
|
||
|
|
setSftpFollowTerminalCwd: s.setSftpFollowTerminalCwd,
|
||
|
|
sftpActiveHost,
|
||
|
|
sftpHostForTab,
|
||
|
|
sftpAutoSync: s.sftpAutoSync,
|
||
|
|
sftpDefaultViewMode: s.sftpDefaultViewMode,
|
||
|
|
sftpDoubleClickBehavior: s.sftpDoubleClickBehavior,
|
||
|
|
sftpFollowTerminalCwd: s.sftpFollowTerminalCwd,
|
||
|
|
sftpInitialLocationForTab: s.sftpInitialLocationForTab,
|
||
|
|
sftpPendingUploadsForTab: s.sftpPendingUploadsForTab,
|
||
|
|
sftpPaneClosedTabIdsRef: s.sftpPaneClosedTabIdsRef,
|
||
|
|
sftpShowHiddenFiles: s.sftpShowHiddenFiles,
|
||
|
|
SftpSidePanel: s.SftpSidePanel,
|
||
|
|
sftpUseCompressedUpload: s.sftpUseCompressedUpload,
|
||
|
|
sidePanelPosition: s.sidePanelPosition,
|
||
|
|
sidePanelWidth: s.sidePanelWidth,
|
||
|
|
sidePanelOpenTabs,
|
||
|
|
sidePanelLayouts,
|
||
|
|
snippetPackages: s.snippetPackages,
|
||
|
|
snippets: s.snippets,
|
||
|
|
updateSnippetPackages: s.updateSnippetPackages,
|
||
|
|
updateSnippets: s.updateSnippets,
|
||
|
|
splitHorizontalHandlersRef: s.splitHorizontalHandlersRef,
|
||
|
|
splitVerticalHandlersRef: s.splitVerticalHandlersRef,
|
||
|
|
sshDebugLogsEnabled: s.sshDebugLogsEnabled,
|
||
|
|
t: s.t,
|
||
|
|
TerminalComposeBar: s.TerminalComposeBar,
|
||
|
|
terminalFontFamilyId: s.terminalFontFamilyId,
|
||
|
|
TerminalPanesHost: s.TerminalPanesHost,
|
||
|
|
terminalSettings: s.terminalSettings,
|
||
|
|
terminalTheme: s.terminalTheme,
|
||
|
|
terminalThemeId: s.terminalThemeId,
|
||
|
|
resolveSessionAppearance: s.resolveSessionAppearance,
|
||
|
|
hostMap: s.hostMap,
|
||
|
|
ThemeSidePanel: s.ThemeSidePanel,
|
||
|
|
Tooltip: s.Tooltip,
|
||
|
|
TooltipContent: s.TooltipContent,
|
||
|
|
TooltipTrigger: s.TooltipTrigger,
|
||
|
|
updateHosts: s.updateHosts,
|
||
|
|
validAIScopeTargetIds: s.validAIScopeTargetIds,
|
||
|
|
workspaceBroadcastHandlersRef: s.workspaceBroadcastHandlersRef,
|
||
|
|
workspaceById,
|
||
|
|
isGlobalBroadcastEnabled: s.isGlobalBroadcastEnabled,
|
||
|
|
canUseGlobalBroadcast: s.canUseGlobalBroadcast,
|
||
|
|
onToggleGlobalBroadcast: s.onToggleGlobalBroadcastRef.current,
|
||
|
|
// AI scope maintenance (merge/dissolve handoff) needs the full list; do not
|
||
|
|
// rely on workspaceById alone — SidePanelStateRoot reads ctx.workspaces.
|
||
|
|
workspaces: s.workspaces,
|
||
|
|
workspaceFocusHandlersRef: s.workspaceFocusHandlersRef,
|
||
|
|
workspaceInnerRef,
|
||
|
|
workspaceOuterRef,
|
||
|
|
workspaceOverlayRef,
|
||
|
|
workspaceRectsById,
|
||
|
|
X: s.X,
|
||
|
|
Zap: s.Zap,
|
||
|
|
// stableRef fields are intentionally omitted from deps — they update every parent render.
|
||
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
|
|
}), [
|
||
|
|
activeHostIdForSidebar,
|
||
|
|
activeResizers,
|
||
|
|
activeSidePanelLayout,
|
||
|
|
activeSidePanelTab,
|
||
|
|
activeTabId,
|
||
|
|
activeTerminalCwd,
|
||
|
|
activeTerminalCwdTrusted,
|
||
|
|
activeTerminalSessionIdForSftp,
|
||
|
|
activeWorkspace,
|
||
|
|
aiContextsByTabId,
|
||
|
|
computeSplitHint,
|
||
|
|
dropHint,
|
||
|
|
focusedHost,
|
||
|
|
focusedSessionId,
|
||
|
|
s.restoreTerminalCwd,
|
||
|
|
handleWorkspaceDrop,
|
||
|
|
handleTerminalContextReaderChange,
|
||
|
|
hibernateHiddenTabs,
|
||
|
|
historySessionId,
|
||
|
|
isFocusMode,
|
||
|
|
isSidePanelOpenForCurrentTab,
|
||
|
|
isTerminalLayerVisible,
|
||
|
|
s.magnifiedPane,
|
||
|
|
resizing,
|
||
|
|
resolveAIExecutorContext,
|
||
|
|
sessionHostsMap,
|
||
|
|
sidePanelLayouts,
|
||
|
|
s.resolvedSessionHostIds,
|
||
|
|
sessions,
|
||
|
|
s.terminalSettings,
|
||
|
|
showHostTreeSidebar,
|
||
|
|
sftpActiveHost,
|
||
|
|
s.sftpFollowTerminalCwd,
|
||
|
|
themeState,
|
||
|
|
workspaceById,
|
||
|
|
s.workspaces,
|
||
|
|
workspaceInnerRef,
|
||
|
|
workspaceOuterRef,
|
||
|
|
workspaceOverlayRef,
|
||
|
|
workspaceRectsById,
|
||
|
|
s.terminalTheme,
|
||
|
|
s.resolveSessionAppearance,
|
||
|
|
s.hostMap,
|
||
|
|
s.isGlobalBroadcastEnabled,
|
||
|
|
s.canUseGlobalBroadcast,
|
||
|
|
s.onToggleGlobalBroadcastRef,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return <TerminalLayerView ctx={ctx} />;
|
||
|
|
}
|