@@ -21,6 +21,8 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
2121 var changeManager : AnyChangeManager
2222 var isEditable : Bool
2323 var sortedIDs : [ RowID ] ?
24+ private( set) var columnDisplayFormats : [ ValueDisplayFormat ? ] = [ ]
25+ private var displayCache : [ RowID : [ String ? ] ] = [ : ]
2426 weak var delegate : ( any DataGridViewDelegate ) ?
2527 weak var activeFKPreviewPopover : NSPopover ?
2628 var dropdownColumns : Set < Int > ?
@@ -150,11 +152,11 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
150152 // When smart detection is toggled off, clear display formats so they stop being applied
151153 if prev. enableSmartValueDetection != settings. enableSmartValueDetection
152154 && !settings. enableSmartValueDetection {
153- self . rowProvider . updateDisplayFormats ( [ ] )
155+ self . updateDisplayFormats ( [ ] )
154156 }
155157
156158 if dataChanged {
157- self . rowProvider . invalidateDisplayCache ( )
159+ self . invalidateDisplayCache ( )
158160 let visibleRect = tableView. visibleRect
159161 let visibleRange = tableView. rows ( in: visibleRect)
160162 if visibleRange. length > 0 {
@@ -200,6 +202,8 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
200202 overlayEditor? . dismiss ( commit: false )
201203 rowProvider = InMemoryRowProvider ( rows: [ ] , columns: [ ] )
202204 rowVisualStateCache. removeAll ( )
205+ displayCache. removeAll ( )
206+ columnDisplayFormats = [ ]
203207 cachedRowCount = 0
204208 cachedColumnCount = 0
205209 sortedIDs = nil
@@ -254,7 +258,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
254258
255259 func applyFullReplace( ) {
256260 guard let tableView else { return }
257- rowProvider . invalidateDisplayCache ( )
261+ displayCache . removeAll ( )
258262 rebuildVisualStateCache ( )
259263 updateCache ( )
260264 tableView. reloadData ( )
@@ -281,13 +285,90 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
281285 return displayIndex
282286 }
283287
288+ func displayValue( forID id: RowID , column: Int , rawValue: String ? , columnType: ColumnType ? ) -> String ? {
289+ if let cachedRow = displayCache [ id] , column >= 0 , column < cachedRow. count, let cached = cachedRow [ column] {
290+ return cached
291+ }
292+ let format = column >= 0 && column < columnDisplayFormats. count ? columnDisplayFormats [ column] : nil
293+ let formatted = CellDisplayFormatter . format ( rawValue, columnType: columnType, displayFormat: format) ?? rawValue
294+
295+ var rowCache = displayCache [ id] ?? [ ]
296+ let neededCount = max ( column + 1 , columnDisplayFormats. count)
297+ if rowCache. count < neededCount {
298+ rowCache. append ( contentsOf: Array ( repeating: nil , count: neededCount - rowCache. count) )
299+ }
300+ if column >= 0 , column < rowCache. count {
301+ rowCache [ column] = formatted
302+ }
303+ displayCache [ id] = rowCache
304+ return formatted
305+ }
306+
307+ func invalidateDisplayCache( ) {
308+ displayCache. removeAll ( )
309+ }
310+
311+ func updateDisplayFormats( _ formats: [ ValueDisplayFormat ? ] ) {
312+ columnDisplayFormats = formats
313+ displayCache. removeAll ( )
314+ }
315+
316+ func syncDisplayFormats( _ formats: [ ValueDisplayFormat ? ] ) {
317+ guard formats != columnDisplayFormats else { return }
318+ columnDisplayFormats = formats
319+ displayCache. removeAll ( )
320+ }
321+
322+ func preWarmDisplayCache( upTo rowCount: Int ) {
323+ let tableRows = tableRowsProvider ( )
324+ let displayCount = sortedIDs? . count ?? tableRows. count
325+ let count = min ( rowCount, displayCount)
326+ guard count > 0 else { return }
327+ for displayIndex in 0 ..< count {
328+ guard let row = displayRow ( at: displayIndex) else { continue }
329+ let id = row. id
330+ guard displayCache [ id] == nil else { continue }
331+ let columnCount = tableRows. columns. count
332+ var rowCache = [ String? ] ( repeating: nil , count: columnCount)
333+ for col in 0 ..< min ( row. values. count, columnCount) {
334+ let columnType = col < tableRows. columnTypes. count ? tableRows. columnTypes [ col] : nil
335+ let format = col < columnDisplayFormats. count ? columnDisplayFormats [ col] : nil
336+ rowCache [ col] = CellDisplayFormatter . format (
337+ row. values [ col] ,
338+ columnType: columnType,
339+ displayFormat: format
340+ ) ?? row. values [ col]
341+ }
342+ displayCache [ id] = rowCache
343+ }
344+ }
345+
346+ private func pruneDisplayCacheToAliveIDs( ) {
347+ guard !displayCache. isEmpty else { return }
348+ let tableRows = tableRowsProvider ( )
349+ var aliveIDs = Set < RowID > ( )
350+ aliveIDs. reserveCapacity ( tableRows. count)
351+ for row in tableRows. rows {
352+ aliveIDs. insert ( row. id)
353+ }
354+ displayCache = displayCache. filter { aliveIDs. contains ( $0. key) }
355+ }
356+
357+ private func invalidateDisplayCache( forDisplayRow displayIndex: Int , column: Int ) {
358+ guard let row = displayRow ( at: displayIndex) else { return }
359+ guard var rowCache = displayCache [ row. id] , column >= 0 , column < rowCache. count else { return }
360+ rowCache [ column] = nil
361+ displayCache [ row. id] = rowCache
362+ }
363+
284364 func applyDelta( _ delta: Delta ) {
285365 switch delta {
286366 case . cellChanged( let row, let column) :
287367 guard let tableView else { return }
288368 let tableColumn = DataGridView . tableColumnIndex ( for: column)
289369 guard row >= 0 , row < tableView. numberOfRows else { return }
290370 guard tableColumn >= 0 , tableColumn < tableView. numberOfColumns else { return }
371+ invalidateDisplayCache ( forDisplayRow: row, column: column)
291372 tableView. reloadData (
292373 forRowIndexes: IndexSet ( integer: row) ,
293374 columnIndexes: IndexSet ( integer: tableColumn)
@@ -304,6 +385,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
304385 if tableColumn >= 0 , tableColumn < tableView. numberOfColumns {
305386 colSet. insert ( tableColumn)
306387 }
388+ invalidateDisplayCache ( forDisplayRow: position. row, column: position. column)
307389 }
308390 guard !rowSet. isEmpty, !colSet. isEmpty else { return }
309391 tableView. reloadData ( forRowIndexes: rowSet, columnIndexes: colSet)
@@ -314,9 +396,11 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
314396 case . rowsRemoved( let indices) :
315397 guard !indices. isEmpty else { return }
316398 removeMissingIDsFromSortedIDs ( )
399+ pruneDisplayCacheToAliveIDs ( )
317400 applyRemovedRows ( indices)
318401 case . columnsReplaced, . fullReplace:
319402 sortedIDs = nil
403+ displayCache. removeAll ( )
320404 applyFullReplace ( )
321405 }
322406 }
@@ -341,7 +425,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
341425 }
342426
343427 func invalidateCachesForUndoRedo( ) {
344- rowProvider . invalidateDisplayCache ( )
428+ displayCache . removeAll ( )
345429 rebuildVisualStateCache ( )
346430 updateCache ( )
347431 }
0 commit comments