fix(app): terminal resize
This commit is contained in:
@@ -156,6 +156,10 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
let serializeAddon: SerializeAddon
|
let serializeAddon: SerializeAddon
|
||||||
let fitAddon: FitAddon
|
let fitAddon: FitAddon
|
||||||
let handleResize: () => void
|
let handleResize: () => void
|
||||||
|
let fitFrame: number | undefined
|
||||||
|
let sizeTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
let pendingSize: { cols: number; rows: number } | undefined
|
||||||
|
let lastSize: { cols: number; rows: number } | undefined
|
||||||
let disposed = false
|
let disposed = false
|
||||||
const cleanups: VoidFunction[] = []
|
const cleanups: VoidFunction[] = []
|
||||||
const start =
|
const start =
|
||||||
@@ -209,6 +213,43 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
const [terminalColors, setTerminalColors] = createSignal<TerminalColors>(getTerminalColors())
|
const [terminalColors, setTerminalColors] = createSignal<TerminalColors>(getTerminalColors())
|
||||||
|
|
||||||
|
const scheduleFit = () => {
|
||||||
|
if (disposed) return
|
||||||
|
if (!fitAddon) return
|
||||||
|
if (fitFrame !== undefined) return
|
||||||
|
|
||||||
|
fitFrame = requestAnimationFrame(() => {
|
||||||
|
fitFrame = undefined
|
||||||
|
if (disposed) return
|
||||||
|
fitAddon.fit()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleSize = (cols: number, rows: number) => {
|
||||||
|
if (disposed) return
|
||||||
|
if (lastSize?.cols === cols && lastSize?.rows === rows) return
|
||||||
|
|
||||||
|
pendingSize = { cols, rows }
|
||||||
|
|
||||||
|
if (!lastSize) {
|
||||||
|
lastSize = pendingSize
|
||||||
|
void pushSize(cols, rows)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeTimer !== undefined) return
|
||||||
|
sizeTimer = setTimeout(() => {
|
||||||
|
sizeTimer = undefined
|
||||||
|
const next = pendingSize
|
||||||
|
if (!next) return
|
||||||
|
pendingSize = undefined
|
||||||
|
if (disposed) return
|
||||||
|
if (lastSize?.cols === next.cols && lastSize?.rows === next.rows) return
|
||||||
|
lastSize = next
|
||||||
|
void pushSize(next.cols, next.rows)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const colors = getTerminalColors()
|
const colors = getTerminalColors()
|
||||||
setTerminalColors(colors)
|
setTerminalColors(colors)
|
||||||
@@ -220,6 +261,16 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
const font = monoFontFamily(settings.appearance.font())
|
const font = monoFontFamily(settings.appearance.font())
|
||||||
if (!term) return
|
if (!term) return
|
||||||
setOptionIfSupported(term, "fontFamily", font)
|
setOptionIfSupported(term, "fontFamily", font)
|
||||||
|
scheduleFit()
|
||||||
|
})
|
||||||
|
|
||||||
|
let zoom = platform.webviewZoom?.()
|
||||||
|
createEffect(() => {
|
||||||
|
const next = platform.webviewZoom?.()
|
||||||
|
if (next === undefined) return
|
||||||
|
if (next === zoom) return
|
||||||
|
zoom = next
|
||||||
|
scheduleFit()
|
||||||
})
|
})
|
||||||
|
|
||||||
const focusTerminal = () => {
|
const focusTerminal = () => {
|
||||||
@@ -263,25 +314,6 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
const once = { value: false }
|
const once = { value: false }
|
||||||
|
|
||||||
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
|
|
||||||
url.searchParams.set("directory", sdk.directory)
|
|
||||||
url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
|
|
||||||
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
|
|
||||||
if (window.__OPENCODE__?.serverPassword) {
|
|
||||||
url.username = "opencode"
|
|
||||||
url.password = window.__OPENCODE__?.serverPassword
|
|
||||||
}
|
|
||||||
const socket = new WebSocket(url)
|
|
||||||
socket.binaryType = "arraybuffer"
|
|
||||||
cleanups.push(() => {
|
|
||||||
if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close()
|
|
||||||
})
|
|
||||||
if (disposed) {
|
|
||||||
cleanup()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ws = socket
|
|
||||||
|
|
||||||
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
|
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
|
||||||
const restoreSize =
|
const restoreSize =
|
||||||
restore &&
|
restore &&
|
||||||
@@ -344,39 +376,16 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
focusTerminal()
|
focusTerminal()
|
||||||
|
|
||||||
const startResize = () => {
|
if (typeof document !== "undefined" && document.fonts) {
|
||||||
fit.observeResize()
|
document.fonts.ready.then(scheduleFit)
|
||||||
handleResize = () => fit.fit()
|
|
||||||
window.addEventListener("resize", handleResize)
|
|
||||||
cleanups.push(() => window.removeEventListener("resize", handleResize))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restore && restoreSize) {
|
const onResize = t.onResize((size) => {
|
||||||
t.write(restore, () => {
|
scheduleSize(size.cols, size.rows)
|
||||||
fit.fit()
|
|
||||||
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
|
|
||||||
startResize()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
fit.fit()
|
|
||||||
if (restore) {
|
|
||||||
t.write(restore, () => {
|
|
||||||
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
startResize()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onResize = t.onResize(async (size) => {
|
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
|
||||||
await pushSize(size.cols, size.rows)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
cleanups.push(() => disposeIfDisposable(onResize))
|
cleanups.push(() => disposeIfDisposable(onResize))
|
||||||
const onData = t.onData((data) => {
|
const onData = t.onData((data) => {
|
||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (ws?.readyState === WebSocket.OPEN) ws.send(data)
|
||||||
socket.send(data)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
cleanups.push(() => disposeIfDisposable(onData))
|
cleanups.push(() => disposeIfDisposable(onData))
|
||||||
const onKey = t.onKey((key) => {
|
const onKey = t.onKey((key) => {
|
||||||
@@ -385,17 +394,64 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
cleanups.push(() => disposeIfDisposable(onKey))
|
cleanups.push(() => disposeIfDisposable(onKey))
|
||||||
|
|
||||||
|
const startResize = () => {
|
||||||
|
fit.observeResize()
|
||||||
|
handleResize = scheduleFit
|
||||||
|
window.addEventListener("resize", handleResize)
|
||||||
|
cleanups.push(() => window.removeEventListener("resize", handleResize))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restore && restoreSize) {
|
||||||
|
t.write(restore, () => {
|
||||||
|
fit.fit()
|
||||||
|
scheduleSize(t.cols, t.rows)
|
||||||
|
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
|
||||||
|
startResize()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fit.fit()
|
||||||
|
scheduleSize(t.cols, t.rows)
|
||||||
|
if (restore) {
|
||||||
|
t.write(restore, () => {
|
||||||
|
if (typeof local.pty.scrollY === "number") t.scrollToLine(local.pty.scrollY)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startResize()
|
||||||
|
}
|
||||||
|
|
||||||
// t.onScroll((ydisp) => {
|
// t.onScroll((ydisp) => {
|
||||||
// console.log("Scroll position:", ydisp)
|
// console.log("Scroll position:", ydisp)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
const url = new URL(sdk.url + `/pty/${local.pty.id}/connect`)
|
||||||
|
url.searchParams.set("directory", sdk.directory)
|
||||||
|
url.searchParams.set("cursor", String(start !== undefined ? start : local.pty.buffer ? -1 : 0))
|
||||||
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
|
||||||
|
if (window.__OPENCODE__?.serverPassword) {
|
||||||
|
url.username = "opencode"
|
||||||
|
url.password = window.__OPENCODE__?.serverPassword
|
||||||
|
}
|
||||||
|
const socket = new WebSocket(url)
|
||||||
|
socket.binaryType = "arraybuffer"
|
||||||
|
ws = socket
|
||||||
|
cleanups.push(() => {
|
||||||
|
if (socket.readyState !== WebSocket.CLOSED && socket.readyState !== WebSocket.CLOSING) socket.close()
|
||||||
|
})
|
||||||
|
if (disposed) {
|
||||||
|
cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
local.onConnect?.()
|
local.onConnect?.()
|
||||||
void pushSize(t.cols, t.rows)
|
scheduleSize(t.cols, t.rows)
|
||||||
}
|
}
|
||||||
socket.addEventListener("open", handleOpen)
|
socket.addEventListener("open", handleOpen)
|
||||||
cleanups.push(() => socket.removeEventListener("open", handleOpen))
|
cleanups.push(() => socket.removeEventListener("open", handleOpen))
|
||||||
|
|
||||||
|
if (socket.readyState === WebSocket.OPEN) handleOpen()
|
||||||
|
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
@@ -462,6 +518,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
disposed = true
|
disposed = true
|
||||||
|
if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
|
||||||
|
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
||||||
output?.flush()
|
output?.flush()
|
||||||
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
||||||
cleanup()
|
cleanup()
|
||||||
@@ -477,7 +535,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
classList={{
|
classList={{
|
||||||
...(local.classList ?? {}),
|
...(local.classList ?? {}),
|
||||||
"select-text": true,
|
"select-text": true,
|
||||||
"size-full px-6 py-3 font-mono": true,
|
"size-full px-6 py-3 font-mono relative overflow-hidden": true,
|
||||||
[local.class ?? ""]: !!local.class,
|
[local.class ?? ""]: !!local.class,
|
||||||
}}
|
}}
|
||||||
{...others}
|
{...others}
|
||||||
|
|||||||
Reference in New Issue
Block a user