feat: terminal now resize appropriately

This commit is contained in:
Hamzat Victor 2024-10-14 10:10:47 +01:00
parent a0183451ad
commit 33fc082217

View File

@ -1,12 +1,13 @@
"use client"; "use client"
import { Terminal } from "@xterm/xterm"; import { Terminal } from "@xterm/xterm"
import { FitAddon } from "@xterm/addon-fit"; import { FitAddon } from "@xterm/addon-fit"
import "./xterm.css"; import "./xterm.css"
import { useEffect, useRef, useState } from "react"; import { ElementRef, useEffect, useRef, useState } from "react"
import { Socket } from "socket.io-client"; import { Socket } from "socket.io-client"
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react"
import { debounce } from "@/lib/utils"
export default function EditorTerminal({ export default function EditorTerminal({
socket, socket,
@ -15,16 +16,17 @@ export default function EditorTerminal({
setTerm, setTerm,
visible, visible,
}: { }: {
socket: Socket; socket: Socket
id: string; id: string
term: Terminal | null; term: Terminal | null
setTerm: (term: Terminal) => void; setTerm: (term: Terminal) => void
visible: boolean; visible: boolean
}) { }) {
const terminalRef = useRef(null); const terminalRef = useRef<ElementRef<"div">>(null)
const fitAddonRef = useRef<FitAddon | null>(null)
useEffect(() => { useEffect(() => {
if (!terminalRef.current) return; if (!terminalRef.current) return
// console.log("new terminal", id, term ? "reusing" : "creating"); // console.log("new terminal", id, term ? "reusing" : "creating");
const terminal = new Terminal({ const terminal = new Terminal({
@ -36,56 +38,80 @@ export default function EditorTerminal({
fontSize: 14, fontSize: 14,
lineHeight: 1.5, lineHeight: 1.5,
letterSpacing: 0, letterSpacing: 0,
}); })
setTerm(terminal); setTerm(terminal)
const dispose = () => {
return () => { terminal.dispose()
if (terminal) terminal.dispose(); }
}; return dispose
}, []); }, [])
useEffect(() => { useEffect(() => {
if (!term) return; if (!term) return
if (!terminalRef.current) return; if (!terminalRef.current) return
const fitAddon = new FitAddon(); if (!fitAddonRef.current) {
term.loadAddon(fitAddon); const fitAddon = new FitAddon()
term.open(terminalRef.current); term.loadAddon(fitAddon)
fitAddon.fit(); term.open(terminalRef.current)
fitAddon.fit()
fitAddonRef.current = fitAddon
}
const disposableOnData = term.onData((data) => { const disposableOnData = term.onData((data) => {
socket.emit("terminalData", id, data); socket.emit("terminalData", id, data)
}); })
const disposableOnResize = term.onResize((dimensions) => { const disposableOnResize = term.onResize((dimensions) => {
// const terminal_size = { fitAddonRef.current?.fit()
// width: dimensions.cols, socket.emit("terminalResize", dimensions)
// height: dimensions.rows, })
// }; const resizeObserver = new ResizeObserver(
fitAddon.fit(); debounce((entries) => {
socket.emit("terminalResize", dimensions); 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 () => { return () => {
disposableOnData.dispose(); disposableOnData.dispose()
disposableOnResize.dispose(); disposableOnResize.dispose()
}; resizeObserver.disconnect()
}, [term, terminalRef.current]); }
}, [term, terminalRef.current])
useEffect(() => { useEffect(() => {
if (!term) return; if (!term) return
const handleTerminalResponse = (response: { id: string; data: string }) => { const handleTerminalResponse = (response: { id: string; data: string }) => {
if (response.id === id) { if (response.id === id) {
term.write(response.data); term.write(response.data)
} }
}; }
socket.on("terminalResponse", handleTerminalResponse); socket.on("terminalResponse", handleTerminalResponse)
return () => { return () => {
socket.off("terminalResponse", handleTerminalResponse); socket.off("terminalResponse", handleTerminalResponse)
}; }
}, [term, id, socket]); }, [term, id, socket])
return ( return (
<> <>
@ -102,5 +128,5 @@ export default function EditorTerminal({
) : null} ) : null}
</div> </div>
</> </>
); )
} }