Skip to content

Commit 22b01fe

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

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

packages/core/src/renderer.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,8 +1905,8 @@ export class CliRenderer extends EventEmitter implements RenderContext {
19051905
this.pendingSplitStartupCursorSeed && this.splitStartupSeedTimeoutId !== null
19061906
const splitTransitionSourceTopLine = this.pendingSplitFooterTransition?.sourceTopLine ?? previousSurfaceTopLine
19071907
const splitTransitionSourceHeight = this.pendingSplitFooterTransition?.sourceHeight ?? prevSplitHeight
1908-
const splitTransitionMode = this.pendingSplitFooterTransition?.mode ??
1909-
(splitWasSettled ? "viewport-scroll" : "clear-stale-rows")
1908+
const splitTransitionMode =
1909+
this.pendingSplitFooterTransition?.mode ?? (splitWasSettled ? "viewport-scroll" : "clear-stale-rows")
19101910

19111911
if (this._terminalIsSetup && leavingSplitFooter) {
19121912
this.clearPendingSplitFooterTransition()
@@ -2696,14 +2696,17 @@ 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 =
2709+
pendingSplitFooterTransition?.sourceHeight ?? previousGeometry.effectiveFooterHeight
27072710

27082711
this._terminalWidth = width
27092712
this._terminalHeight = height
@@ -2721,9 +2724,23 @@ export class CliRenderer extends EventEmitter implements RenderContext {
27212724
const splitFooterActive = this._screenMode === "split-footer"
27222725

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

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)