Files
claudecodeui/src/components/shell/hooks/useShellRuntime.ts
2026-06-26 15:51:10 +03:00

123 lines
3.3 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react';
import type { FitAddon } from '@xterm/addon-fit';
import type { Terminal } from '@xterm/xterm';
import type { UseShellRuntimeOptions, UseShellRuntimeResult } from '../types/types';
import { useShellConnection } from './useShellConnection';
import { useShellTerminal } from './useShellTerminal';
export function useShellRuntime({
selectedProject,
selectedSession,
initialCommand,
isPlainShell,
minimal,
autoConnect,
isRestarting,
onProcessComplete,
onOutputRef,
}: UseShellRuntimeOptions): UseShellRuntimeResult {
const terminalContainerRef = useRef<HTMLDivElement>(null);
const terminalRef = useRef<Terminal | null>(null);
const fitAddonRef = useRef<FitAddon | null>(null);
const wsRef = useRef<WebSocket | null>(null);
const selectedProjectRef = useRef(selectedProject);
const selectedSessionRef = useRef(selectedSession);
const initialCommandRef = useRef(initialCommand);
const isPlainShellRef = useRef(isPlainShell);
const onProcessCompleteRef = useRef(onProcessComplete);
const lastSessionIdRef = useRef<string | null>(selectedSession?.id ?? null);
// Keep mutable values in refs so websocket handlers always read current data.
useEffect(() => {
selectedProjectRef.current = selectedProject;
selectedSessionRef.current = selectedSession;
initialCommandRef.current = initialCommand;
isPlainShellRef.current = isPlainShell;
onProcessCompleteRef.current = onProcessComplete;
}, [selectedProject, selectedSession, initialCommand, isPlainShell, onProcessComplete]);
const closeSocket = useCallback(() => {
const activeSocket = wsRef.current;
if (!activeSocket) {
return;
}
if (
activeSocket.readyState === WebSocket.OPEN ||
activeSocket.readyState === WebSocket.CONNECTING
) {
activeSocket.close();
}
wsRef.current = null;
}, []);
const { isInitialized, clearTerminalScreen, disposeTerminal } = useShellTerminal({
terminalContainerRef,
terminalRef,
fitAddonRef,
wsRef,
selectedProject,
minimal,
isRestarting,
closeSocket,
});
const { isConnected, isConnecting, connectToShell, disconnectFromShell } = useShellConnection({
wsRef,
terminalRef,
fitAddonRef,
selectedProjectRef,
selectedSessionRef,
initialCommandRef,
isPlainShellRef,
onProcessCompleteRef,
isInitialized,
autoConnect,
closeSocket,
clearTerminalScreen,
onOutputRef,
});
useEffect(() => {
if (!isRestarting) {
return;
}
disconnectFromShell();
disposeTerminal();
}, [disconnectFromShell, disposeTerminal, isRestarting]);
useEffect(() => {
if (selectedProject) {
return;
}
disconnectFromShell();
disposeTerminal();
}, [disconnectFromShell, disposeTerminal, selectedProject]);
useEffect(() => {
const currentSessionId = selectedSession?.id ?? null;
if (lastSessionIdRef.current !== currentSessionId && isInitialized) {
disconnectFromShell();
}
lastSessionIdRef.current = currentSessionId;
}, [disconnectFromShell, isInitialized, selectedSession?.id]);
return {
terminalContainerRef,
terminalRef,
wsRef,
isConnected,
isInitialized,
isConnecting,
connectToShell,
disconnectFromShell,
};
}