diff --git a/frontend/components/editor/terminals/terminal.tsx b/frontend/components/editor/terminals/terminal.tsx index 1187acb..83d6195 100644 --- a/frontend/components/editor/terminals/terminal.tsx +++ b/frontend/components/editor/terminals/terminal.tsx @@ -1,12 +1,13 @@ -"use client"; +"use client" -import { Terminal } from "@xterm/xterm"; -import { FitAddon } from "@xterm/addon-fit"; -import "./xterm.css"; +import { Terminal } from "@xterm/xterm" +import { FitAddon } from "@xterm/addon-fit" +import "./xterm.css" -import { useEffect, useRef, useState } from "react"; -import { Socket } from "socket.io-client"; -import { Loader2 } from "lucide-react"; +import { ElementRef, useEffect, useRef, useState } from "react" +import { Socket } from "socket.io-client" +import { Loader2 } from "lucide-react" +import { debounce } from "@/lib/utils" export default function EditorTerminal({ socket, @@ -15,16 +16,17 @@ export default function EditorTerminal({ setTerm, visible, }: { - socket: Socket; - id: string; - term: Terminal | null; - setTerm: (term: Terminal) => void; - visible: boolean; + socket: Socket + id: string + term: Terminal | null + setTerm: (term: Terminal) => void + visible: boolean }) { - const terminalRef = useRef(null); + const terminalRef = useRef>(null) + const fitAddonRef = useRef(null) useEffect(() => { - if (!terminalRef.current) return; + if (!terminalRef.current) return // console.log("new terminal", id, term ? "reusing" : "creating"); const terminal = new Terminal({ @@ -36,56 +38,80 @@ export default function EditorTerminal({ fontSize: 14, lineHeight: 1.5, letterSpacing: 0, - }); + }) - setTerm(terminal); - - return () => { - if (terminal) terminal.dispose(); - }; - }, []); + setTerm(terminal) + const dispose = () => { + terminal.dispose() + } + return dispose + }, []) useEffect(() => { - if (!term) return; + if (!term) return - if (!terminalRef.current) return; - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - term.open(terminalRef.current); - fitAddon.fit(); + if (!terminalRef.current) return + if (!fitAddonRef.current) { + const fitAddon = new FitAddon() + term.loadAddon(fitAddon) + term.open(terminalRef.current) + fitAddon.fit() + fitAddonRef.current = fitAddon + } const disposableOnData = term.onData((data) => { - socket.emit("terminalData", id, data); - }); + socket.emit("terminalData", id, data) + }) const disposableOnResize = term.onResize((dimensions) => { - // const terminal_size = { - // width: dimensions.cols, - // height: dimensions.rows, - // }; - fitAddon.fit(); - socket.emit("terminalResize", dimensions); - }); + fitAddonRef.current?.fit() + socket.emit("terminalResize", dimensions) + }) + const resizeObserver = new ResizeObserver( + debounce((entries) => { + if (!fitAddonRef.current || !terminalRef.current) return + const entry = entries[0] + if (!entry) return + + const { width, height } = entry.contentRect + + // Only call fit if the size has actually changed + if ( + width !== terminalRef.current.offsetWidth || + height !== terminalRef.current.offsetHeight + ) { + try { + fitAddonRef.current.fit() + } catch (err) { + console.error("Error during fit:", err) + } + } + }, 50) // Debounce for 50ms + ) + + // start observing for resize + resizeObserver.observe(terminalRef.current) return () => { - disposableOnData.dispose(); - disposableOnResize.dispose(); - }; - }, [term, terminalRef.current]); + disposableOnData.dispose() + disposableOnResize.dispose() + resizeObserver.disconnect() + } + }, [term, terminalRef.current]) useEffect(() => { - if (!term) return; + if (!term) return const handleTerminalResponse = (response: { id: string; data: string }) => { if (response.id === id) { - term.write(response.data); + term.write(response.data) } - }; - socket.on("terminalResponse", handleTerminalResponse); - + } + socket.on("terminalResponse", handleTerminalResponse) + return () => { - socket.off("terminalResponse", handleTerminalResponse); - }; - }, [term, id, socket]); + socket.off("terminalResponse", handleTerminalResponse) + } + }, [term, id, socket]) return ( <> @@ -102,5 +128,5 @@ export default function EditorTerminal({ ) : null} - ); + ) }