Skip to content

Commit f3934c7

Browse files
committed
renderer: fix resize cleanup during pending footer transition
1 parent 3188693 commit f3934c7

File tree

2 files changed

+77
-3
lines changed

2 files changed

+77
-3
lines changed

packages/core/src/renderer.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2696,14 +2696,16 @@ export class CliRenderer extends EventEmitter implements RenderContext {
26962696
this.flushPendingSplitOutputBeforeTransition()
26972697
}
26982698

2699+
const pendingSplitFooterTransition = this.pendingSplitFooterTransition
26992700
const previousGeometry = calculateRenderGeometry(
27002701
this._screenMode,
27012702
this._terminalWidth,
27022703
this._terminalHeight,
27032704
this._footerHeight,
27042705
)
27052706
const prevWidth = this._terminalWidth
2706-
const visiblePreviousSplitHeight = this.pendingSplitFooterTransition?.sourceHeight ?? previousGeometry.effectiveFooterHeight
2707+
const previousTerminalHeight = this._terminalHeight
2708+
const visiblePreviousSplitHeight = pendingSplitFooterTransition?.sourceHeight ?? previousGeometry.effectiveFooterHeight
27072709

27082710
this._terminalWidth = width
27092711
this._terminalHeight = height
@@ -2721,9 +2723,23 @@ export class CliRenderer extends EventEmitter implements RenderContext {
27212723
const splitFooterActive = this._screenMode === "split-footer"
27222724

27232725
if (splitFooterActive) {
2726+
// Width shrink historically needs a broader scrub band, but if resize interrupts
2727+
// a deferred footer transition we also need to clear from that visible source surface.
2728+
let clearStart: number | null = null
2729+
27242730
if (width < prevWidth && visiblePreviousSplitHeight > 0) {
2725-
const start = Math.max(this._terminalHeight - visiblePreviousSplitHeight * 2, 1)
2726-
const flush = ANSI.moveCursorAndClear(start, 1)
2731+
clearStart = Math.max(previousTerminalHeight - visiblePreviousSplitHeight * 2, 1)
2732+
}
2733+
2734+
if (pendingSplitFooterTransition !== null) {
2735+
clearStart =
2736+
clearStart === null
2737+
? pendingSplitFooterTransition.sourceTopLine
2738+
: Math.min(clearStart, pendingSplitFooterTransition.sourceTopLine)
2739+
}
2740+
2741+
if (clearStart !== null) {
2742+
const flush = ANSI.moveCursorAndClear(clearStart, 1)
27272743
this.writeOut(flush)
27282744
}
27292745

packages/core/src/tests/renderer.console-startup.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,64 @@ test("CliRenderer split-footer resize cleanup uses the visible footer surface wh
11341134
writeOutSpy.mockRestore()
11351135
})
11361136

1137+
test("CliRenderer split-footer resize cleanup uses the visible source top line across width and height resize while a deferred footer transition is pending", async () => {
1138+
const result = await createTestRenderer({
1139+
width: 40,
1140+
height: 10,
1141+
screenMode: "split-footer",
1142+
footerHeight: 4,
1143+
externalOutputMode: "capture-stdout",
1144+
consoleMode: "disabled",
1145+
})
1146+
1147+
renderer = result.renderer
1148+
;(renderer as any)._terminalIsSetup = true
1149+
1150+
const writeOutSpy = spyOn(renderer as any, "writeOut")
1151+
1152+
renderer.footerHeight = 3
1153+
1154+
expect((renderer as any).pendingSplitFooterTransition).toEqual({
1155+
mode: "clear-stale-rows",
1156+
sourceTopLine: 2,
1157+
sourceHeight: 4,
1158+
targetTopLine: 2,
1159+
targetHeight: 3,
1160+
})
1161+
1162+
result.resize(20, 12)
1163+
1164+
expect(writeOutSpy).toHaveBeenCalledTimes(1)
1165+
expect(writeOutSpy.mock.calls[0]?.[0]).toBe(ANSI.moveCursorAndClear(2, 1))
1166+
1167+
writeOutSpy.mockRestore()
1168+
})
1169+
1170+
test("CliRenderer split-footer resize cleanup still clears the visible source surface when only height changes while a deferred footer transition is pending", async () => {
1171+
const result = await createTestRenderer({
1172+
width: 40,
1173+
height: 10,
1174+
screenMode: "split-footer",
1175+
footerHeight: 4,
1176+
externalOutputMode: "capture-stdout",
1177+
consoleMode: "disabled",
1178+
})
1179+
1180+
renderer = result.renderer
1181+
;(renderer as any)._terminalIsSetup = true
1182+
1183+
const writeOutSpy = spyOn(renderer as any, "writeOut")
1184+
1185+
renderer.footerHeight = 3
1186+
1187+
result.resize(40, 12)
1188+
1189+
expect(writeOutSpy).toHaveBeenCalledTimes(1)
1190+
expect(writeOutSpy.mock.calls[0]?.[0]).toBe(ANSI.moveCursorAndClear(2, 1))
1191+
1192+
writeOutSpy.mockRestore()
1193+
})
1194+
11371195
test("CliRenderer split-footer footerHeight changes do not queue deferred transitions while startup cursor seeding blocks the first frame", async () => {
11381196
const result = await createTestRenderer({
11391197
width: 40,

0 commit comments

Comments
 (0)