mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
fix(jetbrains): bound transcript reflow passes during streaming
The reflow chain restarted its pass budget on every height change, so a session that keeps streaming after open reset the budget each EDT cycle and held the panel in a perpetual forgetAll()/re-measure loop, defeating the width-keyed height cache. Add a hard total-pass budget that never resets so the layout can still settle across a few height changes while capping the work a streaming session can trigger. Covers the doLayout re-arm path with a test that latches pendingReflow via a real turn at zero width.
This commit is contained in:
+14
-4
@@ -529,12 +529,12 @@ class SessionMessageListPanel(
|
||||
stable = -1
|
||||
val id = ++seq
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
reflowPass(id, REFLOW_PASSES)
|
||||
reflowPass(id, REFLOW_PASSES, REFLOW_BUDGET)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun reflowPass(id: Int, remaining: Int) {
|
||||
private fun reflowPass(id: Int, remaining: Int, budget: Int) {
|
||||
if (dead || id != seq) return
|
||||
if (turnViews.isEmpty()) return
|
||||
if (width <= 0) {
|
||||
@@ -545,7 +545,12 @@ class SessionMessageListPanel(
|
||||
}
|
||||
val changed = reflow()
|
||||
if (changed) onReflow?.invoke(true)
|
||||
if (remaining <= 0) {
|
||||
// [remaining] restarts whenever the height is still moving, so the chain keeps re-measuring
|
||||
// until the layout settles for REFLOW_PASSES consecutive passes. [budget] never resets: a
|
||||
// session that keeps streaming changes its height every EDT cycle, which would otherwise
|
||||
// reset [remaining] forever and hold the panel in a perpetual forgetAll()/re-measure loop.
|
||||
// The hard budget caps total passes so streaming can't defeat the width-keyed height cache.
|
||||
if (remaining <= 0 || budget <= 0) {
|
||||
stable = -1
|
||||
return
|
||||
}
|
||||
@@ -553,7 +558,7 @@ class SessionMessageListPanel(
|
||||
val left = if (height == stable) remaining - 1 else REFLOW_PASSES
|
||||
stable = height
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
reflowPass(id, left)
|
||||
reflowPass(id, left, budget - 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,5 +638,10 @@ class SessionMessageListPanel(
|
||||
|
||||
private companion object {
|
||||
const val REFLOW_PASSES = 6
|
||||
|
||||
// Hard ceiling on total reflow passes per schedule, independent of height stability. Lets the
|
||||
// layout settle across several height changes (HTML panes reflow asynchronously) while capping
|
||||
// the work a streaming session can trigger, since its height never stabilizes.
|
||||
const val REFLOW_BUDGET = REFLOW_PASSES * 4
|
||||
}
|
||||
}
|
||||
|
||||
+27
@@ -175,6 +175,33 @@ class SessionMessageListPanelTest : BasePlatformTestCase() {
|
||||
assertEquals(80, child.height)
|
||||
}
|
||||
|
||||
fun `test deferred reflow re-arms on first real width layout`() {
|
||||
val child = Growing(20)
|
||||
panel.add(child, 0)
|
||||
// A real turn makes turnViews non-empty, so a zero-width reflow latches pendingReflow instead
|
||||
// of no-opping the way the turnless zero-width test above does.
|
||||
model.upsertMessage(msg("u1", "user"))
|
||||
panel.setSize(0, 400)
|
||||
layout(panel)
|
||||
assertFalse(panel.reflow())
|
||||
|
||||
// The first layout at a real width consumes the parked reflow and schedules a pass.
|
||||
panel.setSize(600, 400)
|
||||
panel.doLayout()
|
||||
|
||||
// Simulate an HTML pane that only reports its settled height after the first layout: the child
|
||||
// stays valid at the same width, so a plain layout keeps the cached height and only the
|
||||
// re-armed forgetAll() reflow re-measures it.
|
||||
child.markValid()
|
||||
child.size = 80
|
||||
|
||||
// Draining the EDT runs the scheduled reflow. Without the doLayout re-arm nothing is queued
|
||||
// and the child would stay at its stale cached height.
|
||||
UIUtil.dispatchAllInvocationEvents()
|
||||
|
||||
assertEquals(80, child.height)
|
||||
}
|
||||
|
||||
fun `test apply style drops cached panel measurements`() {
|
||||
val child = Growing(20)
|
||||
panel.add(child, 0)
|
||||
|
||||
Reference in New Issue
Block a user