diff --git a/bun.lock b/bun.lock index 5b4616ae7..ea8c612d8 100644 --- a/bun.lock +++ b/bun.lock @@ -44,7 +44,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", + "ghostty-web": "0.4.0", "luxon": "catalog:", "marked": "catalog:", "marked-shiki": "catalog:", @@ -496,9 +496,6 @@ "web-tree-sitter", "tree-sitter-bash", ], - "patchedDependencies": { - "ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch", - }, "overrides": { "@types/bun": "catalog:", "@types/node": "catalog:", @@ -2605,7 +2602,7 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], - "ghostty-web": ["ghostty-web@0.3.0", "", {}, "sha512-SAdSHWYF20GMZUB0n8kh1N6Z4ljMnuUqT8iTB2n5FAPswEV10MejEpLlhW/769GL5+BQa1NYwEg9y/XCckV5+A=="], + "ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="], "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], diff --git a/package.json b/package.json index b052e2bbf..65cd0dea8 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,5 @@ "@types/bun": "catalog:", "@types/node": "catalog:" }, - "patchedDependencies": { - "ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch" - } + "patchedDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 81e589e38..12688ac07 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -54,7 +54,7 @@ "@thisbeyond/solid-dnd": "0.7.5", "diff": "catalog:", "fuzzysort": "catalog:", - "ghostty-web": "0.3.0", + "ghostty-web": "0.4.0", "luxon": "catalog:", "marked": "catalog:", "marked-shiki": "catalog:", diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx index e87670f59..4d44d5f7e 100644 --- a/packages/app/src/components/terminal.tsx +++ b/packages/app/src/components/terminal.tsx @@ -70,6 +70,7 @@ export const Terminal = (props: TerminalProps) => { let handleTextareaBlur: () => void let disposed = false const cleanups: VoidFunction[] = [] + let tail = local.pty.tail ?? "" const cleanup = () => { if (!cleanups.length) return @@ -256,6 +257,7 @@ export const Terminal = (props: TerminalProps) => { serializeAddon = serializer t.open(container) + container.addEventListener("pointerdown", handlePointerDown) cleanups.push(() => container.removeEventListener("pointerdown", handlePointerDown)) @@ -276,15 +278,11 @@ export const Terminal = (props: TerminalProps) => { focusTerminal() + fit.fit() + if (local.pty.buffer) { - if (local.pty.rows && local.pty.cols) { - t.resize(local.pty.cols, local.pty.rows) - } t.write(local.pty.buffer, () => { - if (local.pty.scrollY) { - t.scrollToLine(local.pty.scrollY) - } - fitAddon.fit() + if (local.pty.scrollY) t.scrollToLine(local.pty.scrollY) }) } @@ -322,6 +320,19 @@ export const Terminal = (props: TerminalProps) => { // console.log("Scroll position:", ydisp) // }) + const limit = 16_384 + const seed = tail + let sync = !!seed + + const overlap = (data: string) => { + if (!seed) return 0 + const max = Math.min(seed.length, data.length) + for (let i = max; i > 0; i--) { + if (seed.slice(-i) === data.slice(0, i)) return i + } + return 0 + } + const handleOpen = () => { local.onConnect?.() sdk.client.pty @@ -338,7 +349,25 @@ export const Terminal = (props: TerminalProps) => { cleanups.push(() => socket.removeEventListener("open", handleOpen)) const handleMessage = (event: MessageEvent) => { - t.write(event.data) + const data = typeof event.data === "string" ? event.data : "" + if (!data) return + + const next = (() => { + if (!sync) return data + const n = overlap(data) + if (!n) { + sync = false + return data + } + const trimmed = data.slice(n) + if (trimmed) sync = false + return trimmed + })() + + if (!next) return + + t.write(next) + tail = next.length >= limit ? next.slice(-limit) : (tail + next).slice(-limit) } socket.addEventListener("message", handleMessage) cleanups.push(() => socket.removeEventListener("message", handleMessage)) @@ -392,6 +421,7 @@ export const Terminal = (props: TerminalProps) => { props.onCleanup({ ...local.pty, buffer, + tail, rows: t.rows, cols: t.cols, scrollY: t.getViewportY(), diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx index e01b8bc4d..0c383a78d 100644 --- a/packages/app/src/context/terminal.tsx +++ b/packages/app/src/context/terminal.tsx @@ -13,6 +13,7 @@ export type LocalPTY = { cols?: number buffer?: string scrollY?: number + tail?: string } const WORKSPACE_KEY = "__workspace__"