fix(app): terminal disconnect and resync (#14004)
This commit is contained in:
@@ -346,7 +346,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
}
|
}
|
||||||
ghostty = g
|
ghostty = g
|
||||||
term = t
|
term = t
|
||||||
output = terminalWriter((data) => t.write(data))
|
output = terminalWriter((data, done) => t.write(data, done))
|
||||||
|
|
||||||
t.attachCustomKeyEventHandler((event) => {
|
t.attachCustomKeyEventHandler((event) => {
|
||||||
const key = event.key.toLowerCase()
|
const key = event.key.toLowerCase()
|
||||||
@@ -520,9 +520,19 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
disposed = true
|
disposed = true
|
||||||
if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
|
if (fitFrame !== undefined) cancelAnimationFrame(fitFrame)
|
||||||
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
||||||
output?.flush()
|
if (ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) ws.close()
|
||||||
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
|
||||||
cleanup()
|
const finalize = () => {
|
||||||
|
persistTerminal({ term, addon: serializeAddon, cursor, pty: local.pty, onCleanup: props.onCleanup })
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
finalize()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output.flush(finalize)
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ describe("terminalWriter", () => {
|
|||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const scheduled: VoidFunction[] = []
|
const scheduled: VoidFunction[] = []
|
||||||
const writer = terminalWriter(
|
const writer = terminalWriter(
|
||||||
(data) => calls.push(data),
|
(data, done) => {
|
||||||
|
calls.push(data)
|
||||||
|
done?.()
|
||||||
|
},
|
||||||
(flush) => scheduled.push(flush),
|
(flush) => scheduled.push(flush),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,10 +27,38 @@ describe("terminalWriter", () => {
|
|||||||
test("flush is a no-op when empty", () => {
|
test("flush is a no-op when empty", () => {
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
const writer = terminalWriter(
|
const writer = terminalWriter(
|
||||||
(data) => calls.push(data),
|
(data, done) => {
|
||||||
|
calls.push(data)
|
||||||
|
done?.()
|
||||||
|
},
|
||||||
(flush) => flush(),
|
(flush) => flush(),
|
||||||
)
|
)
|
||||||
writer.flush()
|
writer.flush()
|
||||||
expect(calls).toEqual([])
|
expect(calls).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("flush waits for pending write completion", () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
let done: VoidFunction | undefined
|
||||||
|
const writer = terminalWriter(
|
||||||
|
(data, finish) => {
|
||||||
|
calls.push(data)
|
||||||
|
done = finish
|
||||||
|
},
|
||||||
|
(flush) => flush(),
|
||||||
|
)
|
||||||
|
|
||||||
|
writer.push("a")
|
||||||
|
|
||||||
|
let settled = false
|
||||||
|
writer.flush(() => {
|
||||||
|
settled = true
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(calls).toEqual(["a"])
|
||||||
|
expect(settled).toBe(false)
|
||||||
|
|
||||||
|
done?.()
|
||||||
|
expect(settled).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
export function terminalWriter(
|
export function terminalWriter(
|
||||||
write: (data: string) => void,
|
write: (data: string, done?: VoidFunction) => void,
|
||||||
schedule: (flush: VoidFunction) => void = queueMicrotask,
|
schedule: (flush: VoidFunction) => void = queueMicrotask,
|
||||||
) {
|
) {
|
||||||
let chunks: string[] | undefined
|
let chunks: string[] | undefined
|
||||||
|
let waits: VoidFunction[] | undefined
|
||||||
let scheduled = false
|
let scheduled = false
|
||||||
|
let writing = false
|
||||||
|
|
||||||
const flush = () => {
|
const settle = () => {
|
||||||
|
if (scheduled || writing || chunks?.length) return
|
||||||
|
const list = waits
|
||||||
|
if (!list?.length) return
|
||||||
|
waits = undefined
|
||||||
|
for (const fn of list) {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
if (writing) return
|
||||||
scheduled = false
|
scheduled = false
|
||||||
const items = chunks
|
const items = chunks
|
||||||
if (!items?.length) return
|
if (!items?.length) {
|
||||||
|
settle()
|
||||||
|
return
|
||||||
|
}
|
||||||
chunks = undefined
|
chunks = undefined
|
||||||
write(items.join(""))
|
writing = true
|
||||||
|
write(items.join(""), () => {
|
||||||
|
writing = false
|
||||||
|
if (chunks?.length) {
|
||||||
|
if (scheduled) return
|
||||||
|
scheduled = true
|
||||||
|
schedule(run)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const push = (data: string) => {
|
const push = (data: string) => {
|
||||||
@@ -18,9 +44,21 @@ export function terminalWriter(
|
|||||||
if (chunks) chunks.push(data)
|
if (chunks) chunks.push(data)
|
||||||
else chunks = [data]
|
else chunks = [data]
|
||||||
|
|
||||||
if (scheduled) return
|
if (scheduled || writing) return
|
||||||
scheduled = true
|
scheduled = true
|
||||||
schedule(flush)
|
schedule(run)
|
||||||
|
}
|
||||||
|
|
||||||
|
const flush = (done?: VoidFunction) => {
|
||||||
|
if (!scheduled && !writing && !chunks?.length) {
|
||||||
|
done?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (done) {
|
||||||
|
if (waits) waits.push(done)
|
||||||
|
else waits = [done]
|
||||||
|
}
|
||||||
|
run()
|
||||||
}
|
}
|
||||||
|
|
||||||
return { push, flush }
|
return { push, flush }
|
||||||
|
|||||||
Reference in New Issue
Block a user