Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
191 lines
6.0 KiB
TypeScript
191 lines
6.0 KiB
TypeScript
import {
|
|
fromEditorTabId,
|
|
isEditorTabId,
|
|
} from '../state/activeTabStore';
|
|
import { isPluginViewTabId } from '../state/pluginViewTabStore';
|
|
import { applyCustomAccentToTerminalTheme, resolveHostTerminalThemeId } from '../../domain/terminalAppearance';
|
|
import { collectSessionIds } from '../../domain/workspace';
|
|
import type { EditorTabChrome } from '../state/editorTabStore';
|
|
import type { Host, TerminalSession, TerminalTheme, Workspace } from '../../types';
|
|
|
|
function uniqueTabIds(tabIds: readonly string[]): string[] {
|
|
const seen = new Set<string>();
|
|
const uniqueIds: string[] = [];
|
|
for (const tabId of tabIds) {
|
|
if (!tabId || seen.has(tabId)) continue;
|
|
seen.add(tabId);
|
|
uniqueIds.push(tabId);
|
|
}
|
|
return uniqueIds;
|
|
}
|
|
|
|
export function isRootPageTabId(activeTabId: string): boolean {
|
|
return activeTabId === 'vault' || activeTabId === 'sftp';
|
|
}
|
|
|
|
/**
|
|
* Host edit from overlays (Quick Switcher): use the terminal work-surface
|
|
* HostDetailsPanel when a work tab is active; otherwise deep-link into Vault.
|
|
*/
|
|
export function shouldOpenHostEditOnWorkSurface(activeTabId: string): boolean {
|
|
return !isRootPageTabId(activeTabId) && !isPluginViewTabId(activeTabId);
|
|
}
|
|
|
|
export function buildOrderedWorkTabIds(
|
|
tabOrder: readonly string[],
|
|
allTabIds: readonly string[],
|
|
): string[] {
|
|
const uniqueAllTabIds = uniqueTabIds(allTabIds);
|
|
const allTabIdSet = new Set(uniqueAllTabIds);
|
|
const orderedIds = uniqueTabIds(tabOrder.filter((id) => allTabIdSet.has(id)));
|
|
const orderedIdSet = new Set(orderedIds);
|
|
const newIds = uniqueAllTabIds.filter((id) => !orderedIdSet.has(id));
|
|
return [...orderedIds, ...newIds];
|
|
}
|
|
|
|
export function reorderWorkTabIds(
|
|
tabOrder: readonly string[],
|
|
allTabIds: readonly string[],
|
|
draggedId: string,
|
|
targetId: string,
|
|
position: 'before' | 'after' = 'before',
|
|
): string[] {
|
|
if (draggedId === targetId) return buildOrderedWorkTabIds(tabOrder, allTabIds);
|
|
|
|
const currentOrder = buildOrderedWorkTabIds(tabOrder, allTabIds);
|
|
const draggedIndex = currentOrder.indexOf(draggedId);
|
|
const targetIndex = currentOrder.indexOf(targetId);
|
|
if (draggedIndex === -1 || targetIndex === -1) return [...tabOrder];
|
|
|
|
currentOrder.splice(draggedIndex, 1);
|
|
|
|
let nextTargetIndex = targetIndex;
|
|
if (draggedIndex < targetIndex) {
|
|
nextTargetIndex -= 1;
|
|
}
|
|
if (position === 'after') {
|
|
nextTargetIndex += 1;
|
|
}
|
|
|
|
currentOrder.splice(nextTargetIndex, 0, draggedId);
|
|
return currentOrder;
|
|
}
|
|
|
|
export function isHostTreeWorkTabSurface({
|
|
enabled,
|
|
activeTabId,
|
|
logViewIds = new Set(),
|
|
orderedTabs,
|
|
sessionIds,
|
|
workspaceIds,
|
|
}: {
|
|
enabled: boolean;
|
|
activeTabId: string;
|
|
logViewIds?: ReadonlySet<string>;
|
|
orderedTabs: readonly string[];
|
|
sessionIds: ReadonlySet<string>;
|
|
workspaceIds: ReadonlySet<string>;
|
|
}): boolean {
|
|
if (!enabled) return false;
|
|
if (isRootPageTabId(activeTabId)) return false;
|
|
if (isPluginViewTabId(activeTabId)) return false;
|
|
return orderedTabs.includes(activeTabId)
|
|
|| isEditorTabId(activeTabId)
|
|
|| logViewIds.has(activeTabId)
|
|
|| sessionIds.has(activeTabId)
|
|
|| workspaceIds.has(activeTabId);
|
|
}
|
|
|
|
export function isTerminalContentTabSurface({
|
|
activeTabId,
|
|
sessionIds,
|
|
workspaceIds,
|
|
}: {
|
|
activeTabId: string;
|
|
sessionIds: ReadonlySet<string>;
|
|
workspaceIds: ReadonlySet<string>;
|
|
}): boolean {
|
|
return sessionIds.has(activeTabId) || workspaceIds.has(activeTabId);
|
|
}
|
|
|
|
export function resolveWorkspaceTargetSession(
|
|
workspace: Workspace,
|
|
sessions: readonly TerminalSession[],
|
|
): TerminalSession | undefined {
|
|
const sessionById = new Map(sessions.map((session) => [session.id, session]));
|
|
return resolveWorkspaceTargetSessionFromMap(workspace, sessionById);
|
|
}
|
|
|
|
export function resolveWorkspaceTargetSessionFromMap(
|
|
workspace: Workspace,
|
|
sessionById: ReadonlyMap<string, TerminalSession>,
|
|
): TerminalSession | undefined {
|
|
const orderedSessionIds = collectSessionIds(workspace.root);
|
|
const workspaceSessionIdSet = new Set(orderedSessionIds);
|
|
const focusedSession = workspace.focusedSessionId
|
|
? sessionById.get(workspace.focusedSessionId)
|
|
: undefined;
|
|
const validFocusedSession = focusedSession && workspaceSessionIdSet.has(focusedSession.id)
|
|
? focusedSession
|
|
: undefined;
|
|
if (validFocusedSession) return validFocusedSession;
|
|
for (const sessionId of orderedSessionIds) {
|
|
const session = sessionById.get(sessionId);
|
|
if (session) return session;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function resolveWorkTabActiveHostId({
|
|
activeTabId,
|
|
editorTabs,
|
|
sessions,
|
|
workspaces,
|
|
}: {
|
|
activeTabId: string;
|
|
editorTabs: readonly EditorTabChrome[];
|
|
sessions: readonly TerminalSession[];
|
|
workspaces: readonly Workspace[];
|
|
}): string | null {
|
|
if (isEditorTabId(activeTabId)) {
|
|
const editorId = fromEditorTabId(activeTabId);
|
|
return editorTabs.find((tab) => tab.id === editorId)?.hostId ?? null;
|
|
}
|
|
|
|
const activeSession = sessions.find((session) => session.id === activeTabId);
|
|
if (activeSession) return activeSession.hostId ?? null;
|
|
|
|
const activeWorkspace = workspaces.find((workspace) => workspace.id === activeTabId);
|
|
if (!activeWorkspace) return null;
|
|
|
|
const targetSession = resolveWorkspaceTargetSession(activeWorkspace, sessions);
|
|
return targetSession?.hostId ?? null;
|
|
}
|
|
|
|
export function resolveWorkTabHostTreeTheme({
|
|
activeHostId,
|
|
accentMode,
|
|
currentTerminalTheme,
|
|
customAccent,
|
|
followAppTerminalTheme,
|
|
hostById,
|
|
themeById,
|
|
}: {
|
|
activeHostId: string | null;
|
|
accentMode: 'theme' | 'custom';
|
|
currentTerminalTheme: TerminalTheme;
|
|
customAccent: string;
|
|
followAppTerminalTheme: boolean;
|
|
hostById: ReadonlyMap<string, Host>;
|
|
themeById: ReadonlyMap<string, TerminalTheme>;
|
|
}): TerminalTheme {
|
|
if (!activeHostId || followAppTerminalTheme) {
|
|
return applyCustomAccentToTerminalTheme(currentTerminalTheme, accentMode, customAccent);
|
|
}
|
|
|
|
const host = hostById.get(activeHostId) ?? null;
|
|
const themeId = resolveHostTerminalThemeId(host, currentTerminalTheme.id);
|
|
const baseTheme = themeById.get(themeId) ?? currentTerminalTheme;
|
|
return applyCustomAccentToTerminalTheme(baseTheme, accentMode, customAccent);
|
|
}
|