[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
213
components/terminal/useTerminalHibernateEffect.ts
Normal file
213
components/terminal/useTerminalHibernateEffect.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import {
|
||||
canHibernateTerminalRuntimeStatus,
|
||||
type TerminalHibernateWakePayload,
|
||||
} from "../../domain/terminalHibernate";
|
||||
import { logger } from "../../lib/logger";
|
||||
import {
|
||||
resolvePaneVisible,
|
||||
subscribePaneVisible,
|
||||
} from "./paneVisibilityStore";
|
||||
import type { TerminalSession } from "../../types";
|
||||
|
||||
type UseTerminalHibernateEffectOptions = {
|
||||
sessionId: string;
|
||||
isVisible: boolean;
|
||||
isVisibleRef: React.MutableRefObject<boolean>;
|
||||
getSessionConnectedRef: React.MutableRefObject<() => boolean>;
|
||||
status: TerminalSession["status"];
|
||||
isSearchOpen: boolean;
|
||||
hibernateEnabled: boolean;
|
||||
hibernateDelayMs: number;
|
||||
fileTransferActive: boolean;
|
||||
hibernatedRef: React.MutableRefObject<boolean>;
|
||||
softHiddenRef: React.MutableRefObject<boolean>;
|
||||
hibernatePendingBufferRef: React.MutableRefObject<string>;
|
||||
hibernateSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateViewportSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateScrollbackSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateContextSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateContextViewportSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateContextScrollbackSnapshotRef: React.MutableRefObject<string>;
|
||||
hibernateAlternateScreenRef: React.MutableRefObject<boolean>;
|
||||
hasRuntimeRef: React.MutableRefObject<boolean>;
|
||||
onHibernate: () => void;
|
||||
onSoftHideWake: () => void;
|
||||
onWake: (
|
||||
getPayload: () => TerminalHibernateWakePayload,
|
||||
options: { sessionConnected: boolean },
|
||||
) => boolean | Promise<boolean>;
|
||||
};
|
||||
|
||||
export function useTerminalHibernateEffect({
|
||||
sessionId,
|
||||
isVisible,
|
||||
isVisibleRef,
|
||||
getSessionConnectedRef,
|
||||
status,
|
||||
isSearchOpen,
|
||||
hibernateEnabled,
|
||||
hibernateDelayMs,
|
||||
fileTransferActive,
|
||||
hibernatedRef,
|
||||
softHiddenRef,
|
||||
hibernatePendingBufferRef,
|
||||
hibernateSnapshotRef,
|
||||
hibernateViewportSnapshotRef,
|
||||
hibernateScrollbackSnapshotRef,
|
||||
hibernateContextSnapshotRef,
|
||||
hibernateContextViewportSnapshotRef,
|
||||
hibernateContextScrollbackSnapshotRef,
|
||||
hibernateAlternateScreenRef,
|
||||
hasRuntimeRef,
|
||||
onHibernate,
|
||||
onSoftHideWake,
|
||||
onWake,
|
||||
}: UseTerminalHibernateEffectOptions): void {
|
||||
const hiddenSinceRef = useRef<number | null>(null);
|
||||
const hibernateTimerRef = useRef<number | null>(null);
|
||||
const paneVisibleRef = useRef(resolvePaneVisible(sessionId, isVisible));
|
||||
const onHibernateRef = useRef(onHibernate);
|
||||
const onSoftHideWakeRef = useRef(onSoftHideWake);
|
||||
const onWakeRef = useRef(onWake);
|
||||
onHibernateRef.current = onHibernate;
|
||||
onSoftHideWakeRef.current = onSoftHideWake;
|
||||
onWakeRef.current = onWake;
|
||||
|
||||
useEffect(() => {
|
||||
const resolveVisible = () => resolvePaneVisible(sessionId, isVisibleRef.current);
|
||||
|
||||
const clearHibernateTimer = () => {
|
||||
if (hibernateTimerRef.current !== null) {
|
||||
window.clearTimeout(hibernateTimerRef.current);
|
||||
hibernateTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const clearHibernateState = () => {
|
||||
hibernateSnapshotRef.current = "";
|
||||
hibernateViewportSnapshotRef.current = "";
|
||||
hibernateScrollbackSnapshotRef.current = "";
|
||||
hibernateContextSnapshotRef.current = "";
|
||||
hibernateContextViewportSnapshotRef.current = "";
|
||||
hibernateContextScrollbackSnapshotRef.current = "";
|
||||
hibernatePendingBufferRef.current = "";
|
||||
hibernateAlternateScreenRef.current = false;
|
||||
hibernatedRef.current = false;
|
||||
};
|
||||
|
||||
const scheduleHibernate = () => {
|
||||
if (!hibernateEnabled) return;
|
||||
clearHibernateTimer();
|
||||
if (hibernatedRef.current || !hasRuntimeRef.current) return;
|
||||
if (!canHibernateTerminalRuntimeStatus(status)) return;
|
||||
if (isSearchOpen) return;
|
||||
if (fileTransferActive) return;
|
||||
|
||||
hiddenSinceRef.current = Date.now();
|
||||
const hiddenAt = hiddenSinceRef.current;
|
||||
hibernateTimerRef.current = window.setTimeout(() => {
|
||||
hibernateTimerRef.current = null;
|
||||
if (hiddenSinceRef.current !== hiddenAt) return;
|
||||
if (resolveVisible()) return;
|
||||
onHibernateRef.current();
|
||||
}, hibernateDelayMs);
|
||||
};
|
||||
|
||||
const tryWake = () => {
|
||||
if (softHiddenRef.current) {
|
||||
onSoftHideWakeRef.current();
|
||||
return;
|
||||
}
|
||||
if (!hibernatedRef.current) return;
|
||||
|
||||
const sessionConnected = getSessionConnectedRef.current();
|
||||
const getPayload = (): TerminalHibernateWakePayload => ({
|
||||
snapshot: hibernateSnapshotRef.current,
|
||||
viewportSnapshot: hibernateViewportSnapshotRef.current || hibernateSnapshotRef.current,
|
||||
scrollbackSnapshot: hibernateScrollbackSnapshotRef.current,
|
||||
pendingBuffer: hibernatePendingBufferRef.current,
|
||||
alternateScreen: hibernateAlternateScreenRef.current,
|
||||
});
|
||||
logger.info("[Terminal] Waking from hibernate", {
|
||||
sessionId,
|
||||
snapshotChars: hibernateSnapshotRef.current.length,
|
||||
viewportChars: hibernateViewportSnapshotRef.current.length,
|
||||
scrollbackChars: hibernateScrollbackSnapshotRef.current.length,
|
||||
pendingChars: hibernatePendingBufferRef.current.length,
|
||||
sessionConnected,
|
||||
});
|
||||
void Promise.resolve(onWakeRef.current(getPayload, { sessionConnected })).then((accepted) => {
|
||||
if (accepted !== false) {
|
||||
clearHibernateState();
|
||||
if (!resolveVisible()) {
|
||||
scheduleHibernate();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const applyVisibility = (visible: boolean) => {
|
||||
paneVisibleRef.current = visible;
|
||||
// Keep the write/recovery ref current even when Terminal memo used to skip
|
||||
// isVisible-only updates, and even when hibernate itself is disabled.
|
||||
isVisibleRef.current = visible;
|
||||
|
||||
if (visible) {
|
||||
hiddenSinceRef.current = null;
|
||||
clearHibernateTimer();
|
||||
tryWake();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hibernateEnabled) {
|
||||
scheduleHibernate();
|
||||
}
|
||||
};
|
||||
|
||||
if (!hibernateEnabled) {
|
||||
// Turning hibernate off must wake already soft-hidden / hibernated panes
|
||||
// immediately; waiting for the next tab select would leave the setting
|
||||
// ineffective for currently hidden sessions.
|
||||
clearHibernateTimer();
|
||||
if (hibernatedRef.current || softHiddenRef.current) {
|
||||
tryWake();
|
||||
}
|
||||
}
|
||||
|
||||
applyVisibility(resolveVisible());
|
||||
|
||||
const unsubscribe = subscribePaneVisible(sessionId, () => {
|
||||
const visible = resolveVisible();
|
||||
if (visible === paneVisibleRef.current) return;
|
||||
applyVisibility(visible);
|
||||
});
|
||||
|
||||
return () => {
|
||||
clearHibernateTimer();
|
||||
unsubscribe();
|
||||
};
|
||||
}, [
|
||||
fileTransferActive,
|
||||
getSessionConnectedRef,
|
||||
hasRuntimeRef,
|
||||
hibernateAlternateScreenRef,
|
||||
hibernateDelayMs,
|
||||
hibernateEnabled,
|
||||
hibernateContextScrollbackSnapshotRef,
|
||||
hibernateContextSnapshotRef,
|
||||
hibernateContextViewportSnapshotRef,
|
||||
hibernatePendingBufferRef,
|
||||
hibernateScrollbackSnapshotRef,
|
||||
hibernateSnapshotRef,
|
||||
hibernateViewportSnapshotRef,
|
||||
hibernatedRef,
|
||||
isSearchOpen,
|
||||
isVisible,
|
||||
isVisibleRef,
|
||||
sessionId,
|
||||
softHiddenRef,
|
||||
status,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user