@@ -58,6 +58,7 @@ import { measureTextCells } from "../layout/textMeasure.js";
5858import type { Rect } from "../layout/types.js" ;
5959import { PERF_DETAIL_ENABLED , PERF_ENABLED , perfMarkEnd , perfMarkStart } from "../perf/perf.js" ;
6060import { type CursorInfo , renderToDrawlist } from "../renderer/renderToDrawlist.js" ;
61+ import { getRuntimeNodeDamageRect } from "../renderer/renderToDrawlist/damageBounds.js" ;
6162import { renderTree } from "../renderer/renderToDrawlist/renderTree.js" ;
6263import { DEFAULT_BASE_STYLE } from "../renderer/renderToDrawlist/textStyle.js" ;
6364import { type CommitOk , type RuntimeInstance , commitVNodeTree } from "../runtime/commit.js" ;
@@ -522,6 +523,10 @@ export class WidgetRenderer<S> {
522523 private readonly _pooledSplitPaneChildRectsById = new Map < string , readonly Rect [ ] > ( ) ;
523524 private readonly _prevFrameRectByInstanceId = new Map < InstanceId , Rect > ( ) ;
524525 private readonly _prevFrameRectById = new Map < string , Rect > ( ) ;
526+ private readonly _pooledDamageRectByInstanceId = new Map < InstanceId , Rect > ( ) ;
527+ private readonly _pooledDamageRectById = new Map < string , Rect > ( ) ;
528+ private readonly _prevFrameDamageRectByInstanceId = new Map < InstanceId , Rect > ( ) ;
529+ private readonly _prevFrameDamageRectById = new Map < string , Rect > ( ) ;
525530 private readonly _pooledDamageRects : Rect [ ] = [ ] ;
526531 private readonly _pooledMergedDamageRects : Rect [ ] = [ ] ;
527532 private _hasRenderedFrame = false ;
@@ -2495,12 +2500,12 @@ export class WidgetRenderer<S> {
24952500 }
24962501
24972502 private appendDamageRectForInstanceId ( instanceId : InstanceId ) : boolean {
2498- const current = this . _pooledRectByInstanceId . get ( instanceId ) ;
2503+ const current = this . _pooledDamageRectByInstanceId . get ( instanceId ) ;
24992504 if ( current && current . w > 0 && current . h > 0 ) {
25002505 this . _pooledDamageRects . push ( current ) ;
25012506 return true ;
25022507 }
2503- const prev = this . _prevFrameRectByInstanceId . get ( instanceId ) ;
2508+ const prev = this . _prevFrameDamageRectByInstanceId . get ( instanceId ) ;
25042509 if ( prev && prev . w > 0 && prev . h > 0 ) {
25052510 this . _pooledDamageRects . push ( prev ) ;
25062511 return true ;
@@ -2509,19 +2514,46 @@ export class WidgetRenderer<S> {
25092514 }
25102515
25112516 private appendDamageRectForId ( id : string ) : boolean {
2512- const current = this . _pooledRectById . get ( id ) ;
2517+ const current = this . _pooledDamageRectById . get ( id ) ;
25132518 if ( current && current . w > 0 && current . h > 0 ) {
25142519 this . _pooledDamageRects . push ( current ) ;
25152520 return true ;
25162521 }
2517- const prev = this . _prevFrameRectById . get ( id ) ;
2522+ const prev = this . _prevFrameDamageRectById . get ( id ) ;
25182523 if ( prev && prev . w > 0 && prev . h > 0 ) {
25192524 this . _pooledDamageRects . push ( prev ) ;
25202525 return true ;
25212526 }
25222527 return false ;
25232528 }
25242529
2530+ private refreshDamageRectIndexesForLayoutSkippedCommit ( runtimeRoot : RuntimeInstance ) : void {
2531+ this . _pooledDamageRectByInstanceId . clear ( ) ;
2532+ this . _pooledDamageRectById . clear ( ) ;
2533+ this . _pooledRuntimeStack . length = 0 ;
2534+ this . _pooledRuntimeStack . push ( runtimeRoot ) ;
2535+
2536+ while ( this . _pooledRuntimeStack . length > 0 ) {
2537+ const node = this . _pooledRuntimeStack . pop ( ) ;
2538+ if ( ! node ) continue ;
2539+
2540+ const rect = this . _pooledRectByInstanceId . get ( node . instanceId ) ;
2541+ if ( rect ) {
2542+ const damageRect = getRuntimeNodeDamageRect ( node , rect ) ;
2543+ this . _pooledDamageRectByInstanceId . set ( node . instanceId , damageRect ) ;
2544+ const id = ( node . vnode as { props ?: { id ?: unknown } } ) . props ?. id ;
2545+ if ( typeof id === "string" && id . length > 0 && ! this . _pooledDamageRectById . has ( id ) ) {
2546+ this . _pooledDamageRectById . set ( id , damageRect ) ;
2547+ }
2548+ }
2549+
2550+ for ( let i = node . children . length - 1 ; i >= 0 ; i -- ) {
2551+ const child = node . children [ i ] ;
2552+ if ( child ) this . _pooledRuntimeStack . push ( child ) ;
2553+ }
2554+ }
2555+ }
2556+
25252557 private collectSpinnerDamageRects ( runtimeRoot : RuntimeInstance , layoutRoot : LayoutTree ) : void {
25262558 this . _pooledRuntimeStack . length = 0 ;
25272559 this . _pooledLayoutStack . length = 0 ;
@@ -2594,6 +2626,14 @@ export class WidgetRenderer<S> {
25942626 this . _prevFrameRectById . set ( id , rect ) ;
25952627 }
25962628 }
2629+ this . _prevFrameDamageRectByInstanceId . clear ( ) ;
2630+ for ( const [ instanceId , rect ] of this . _pooledDamageRectByInstanceId ) {
2631+ this . _prevFrameDamageRectByInstanceId . set ( instanceId , rect ) ;
2632+ }
2633+ this . _prevFrameDamageRectById . clear ( ) ;
2634+ for ( const [ id , rect ] of this . _pooledDamageRectById ) {
2635+ this . _prevFrameDamageRectById . set ( id , rect ) ;
2636+ }
25972637 this . _hasRenderedFrame = true ;
25982638 this . _lastRenderedViewport = Object . freeze ( { cols : viewport . cols , rows : viewport . rows } ) ;
25992639 this . _lastRenderedThemeRef = theme ;
@@ -2747,7 +2787,9 @@ export class WidgetRenderer<S> {
27472787 nextLayoutTree ,
27482788 this . committedRoot ,
27492789 this . _pooledRectByInstanceId ,
2790+ this . _pooledDamageRectByInstanceId ,
27502791 this . _pooledRectById ,
2792+ this . _pooledDamageRectById ,
27512793 this . _pooledSplitPaneChildRectsById ,
27522794 this . _pooledLayoutStack ,
27532795 this . _pooledRuntimeStack ,
@@ -2764,6 +2806,9 @@ export class WidgetRenderer<S> {
27642806 detail : "widgetRenderer: missing layout tree" ,
27652807 } ;
27662808 }
2809+ if ( doCommit && ! doLayout ) {
2810+ this . refreshDamageRectIndexesForLayoutSkippedCommit ( this . committedRoot ) ;
2811+ }
27672812
27682813 if ( doCommit ) {
27692814 const canSkipMetadataCollect =
0 commit comments