"use client"; import { useEffect, useRef, useState, useCallback } from "react"; import Ansi from "ansi-to-react"; import type { useTerminal } from "@/hooks/useTerminal"; interface TerminalInstanceProps { terminal: ReturnType; } /** * TerminalInstance — renders terminal output with ANSI color support. * * Uses ansi-to-react for ANSI escape code rendering. * xterm.js integration can be added later for full terminal emulation. */ export function TerminalInstance({ terminal }: TerminalInstanceProps) { const { isElectron, connected, exited, create, write, setOnData } = terminal; const scrollRef = useRef(null); const inputRef = useRef(null); const [output, setOutput] = useState(""); const bufferRef = useRef(""); const rafRef = useRef(null); // Flush buffered output via rAF to avoid excessive re-renders const flush = useCallback(() => { rafRef.current = null; setOutput(bufferRef.current); }, []); // Subscribe to PTY output useEffect(() => { setOnData((data: string) => { bufferRef.current += data; if (rafRef.current === null) { rafRef.current = requestAnimationFrame(flush); } }); return () => { if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); }; }, [setOnData, flush]); // Auto-scroll on new output useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [output]); // Create terminal on mount useEffect(() => { if (isElectron && !connected && !exited) { create(120, 30); } }, [isElectron, connected, exited, create]); // Focus input when terminal connects useEffect(() => { inputRef.current?.focus(); }, [connected]); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { const value = inputRef.current?.value || ''; write(value + '\n'); if (inputRef.current) inputRef.current.value = ''; } }; const handleContainerClick = () => { inputRef.current?.focus(); }; return (
{output}
$
); }