Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 0
Upgrade xterm to v6 with improved React integration and configurable database path#9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
d54a1a95e936e07f9f74a7f632e9c8e9528File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { useEffect, useMemo, type RefObject } from "react"; | ||
| import { AttachAddon } from "@xterm/addon-attach"; | ||
| import { FitAddon } from "@xterm/addon-fit"; | ||
| import { useXTerm } from "./useXTerm"; | ||
| import { useSessionSocket } from "./useSessionSocket"; | ||
| import { TERMINAL_OPTIONS } from "../lib/terminalConfig"; | ||
| export function useForgeTerminal(wsUrl: string | null): { | ||
| containerRef: RefObject<HTMLDivElement>; | ||
| } { | ||
| const fitAddon = useMemo(() => new FitAddon(), []); | ||
| const { ref, instance } = useXTerm(TERMINAL_OPTIONS); | ||
| const { send } = useSessionSocket(); | ||
| // terminalId is the last path segment: /ws/agent/<id> or /ws/shell/<id> | ||
| const parts = wsUrl?.split("/"); | ||
| const terminalId = parts ? parts[parts.length - 1] : null; | ||
| // Load FitAddon once when terminal is ready | ||
| useEffect(() => { | ||
| if (!instance) return; | ||
| instance.loadAddon(fitAddon); | ||
| }, [instance, fitAddon]); | ||
| // ResizeObserver → fit + send resize over session channel | ||
| useEffect(() => { | ||
| if (!instance || !ref.current) return; | ||
| const container = ref.current; | ||
| const safeFit = () => { | ||
| try { | ||
| fitAddon.fit(); | ||
| } catch {} | ||
| }; | ||
| const observer = new ResizeObserver(() => { | ||
| safeFit(); | ||
| if (terminalId) { | ||
| send({ type: "resize", agentId: terminalId, cols: instance.cols, rows: instance.rows }); | ||
| } | ||
| }); | ||
| observer.observe(container); | ||
| requestAnimationFrame(safeFit); | ||
| return () => observer.disconnect(); | ||
| }, [instance, ref, fitAddon, send, terminalId]); | ||
| // Data WS — AttachAddon owns it entirely (pure raw PTY) | ||
| useEffect(() => { | ||
| if (!wsUrl || !instance) return; | ||
| let disposed = false; | ||
| let attachAddon: AttachAddon | null = null; | ||
| let dataSocket: WebSocket | null = null; | ||
| let reconnectTimer: ReturnType<typeof setTimeout> | null = null; | ||
| const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; | ||
| const connect = () => { | ||
| if (disposed) return; | ||
| const ws = new WebSocket(`${protocol}//${window.location.host}${wsUrl}`); | ||
| dataSocket = ws; | ||
| ws.addEventListener("open", () => { | ||
| instance.clear(); | ||
| attachAddon?.dispose(); | ||
| attachAddon = new AttachAddon(ws); | ||
| instance.loadAddon(attachAddon); | ||
| }); | ||
| ws.addEventListener("close", () => { | ||
| attachAddon?.dispose(); | ||
| attachAddon = null; | ||
| instance.write("\r\n\x1b[33m[disconnected]\x1b[0m\r\n"); | ||
| if (!disposed) reconnectTimer = setTimeout(connect, 3000); | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| ws.addEventListener("error", () => { | ||
| instance.write("\r\n\x1b[31m[connection error]\x1b[0m\r\n"); | ||
| }); | ||
| }; | ||
| connect(); | ||
| return () => { | ||
| disposed = true; | ||
| if (reconnectTimer !== null) clearTimeout(reconnectTimer); | ||
| attachAddon?.dispose(); | ||
| dataSocket?.close(); | ||
| }; | ||
| }, [wsUrl, instance]); | ||
| return { containerRef: ref as RefObject<HTMLDivElement> }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.