Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 30 additions & 36 deletions TablePro/Views/Main/Child/MainEditorContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ private struct SortedRowsCache {
let resultVersion: Int
}

/// Per-tab row provider cache entry — groups all cache-invalidation keys together
private struct RowProviderCacheEntry {
let provider: InMemoryRowProvider
let resultVersion: Int
let metadataVersion: Int
let sortState: SortState
}

/// Main editor content with tab bar and content switching
struct MainEditorContentView: View {
// MARK: - Dependencies
Expand Down Expand Up @@ -69,8 +61,7 @@ struct MainEditorContentView: View {

@State private var sortCache: [UUID: SortedRowsCache] = [:]

// Per-tab row provider cache — avoids recreation on every SwiftUI render.
@State private var tabProviderCache: [UUID: RowProviderCacheEntry] = [:]
@State private var providerCache = RowProviderCache()
@State private var cachedChangeManager: AnyChangeManager?
@State private var erDiagramViewModels: [UUID: ERDiagramViewModel] = [:]
@State private var serverDashboardViewModels: [UUID: ServerDashboardViewModel] = [:]
Expand Down Expand Up @@ -127,26 +118,28 @@ struct MainEditorContentView: View {
favoriteDialogQuery = FavoriteDialogQuery(query: query)
}
.onChange(of: tabManager.tabIds) { _, newIds in
guard !sortCache.isEmpty || !tabProviderCache.isEmpty || !erDiagramViewModels.isEmpty
guard !sortCache.isEmpty || !providerCache.isEmpty || !erDiagramViewModels.isEmpty
|| !serverDashboardViewModels.isEmpty else {
coordinator.cleanupSortCache(openTabIds: Set(newIds))
return
}
let openTabIds = Set(newIds)
sortCache = sortCache.filter { openTabIds.contains($0.key) }
coordinator.cleanupSortCache(openTabIds: openTabIds)
tabProviderCache = tabProviderCache.filter { openTabIds.contains($0.key) }
providerCache.retain(tabIds: openTabIds)
erDiagramViewModels = erDiagramViewModels.filter { openTabIds.contains($0.key) }
serverDashboardViewModels = serverDashboardViewModels.filter { openTabIds.contains($0.key) }
}
.onChange(of: tabManager.selectedTabId) { _, newId in
.onChange(of: tabManager.selectedTabId) { _, _ in
updateHasQueryText()

guard let newId, let tab = tabManager.selectedTab else { return }
let cached = tabProviderCache[newId]
if cached?.resultVersion != tab.resultVersion
|| cached?.metadataVersion != tab.metadataVersion
{
guard let tab = tabManager.selectedTab else { return }
if providerCache.provider(
for: tab.id,
resultVersion: tab.resultVersion,
metadataVersion: tab.metadataVersion,
sortState: tab.sortState
) == nil {
cacheRowProvider(for: tab)
}
}
Expand All @@ -157,7 +150,7 @@ struct MainEditorContentView: View {
cacheRowProvider(for: tab)
}
coordinator.onTeardown = { [self] in
tabProviderCache.removeAll()
providerCache.removeAll()
sortCache.removeAll()
cachedChangeManager = nil
}
Expand Down Expand Up @@ -570,32 +563,33 @@ struct MainEditorContentView: View {

private func rowProvider(for tab: QueryTab) -> InMemoryRowProvider {
if tab.rowBuffer.isEvicted {
Task { @MainActor in tabProviderCache.removeValue(forKey: tab.id) }
providerCache.remove(for: tab.id)
return makeRowProvider(for: tab)
}
if let entry = tabProviderCache[tab.id],
entry.resultVersion == tab.resultVersion,
entry.metadataVersion == tab.metadataVersion,
entry.sortState == tab.sortState
{
return entry.provider
if let cached = providerCache.provider(
for: tab.id,
resultVersion: tab.resultVersion,
metadataVersion: tab.metadataVersion,
sortState: tab.sortState
) {
return cached
}
let provider = makeRowProvider(for: tab)
Task { @MainActor in
tabProviderCache[tab.id] = RowProviderCacheEntry(
provider: provider,
resultVersion: tab.resultVersion,
metadataVersion: tab.metadataVersion,
sortState: tab.sortState
)
}
providerCache.store(
provider,
for: tab.id,
resultVersion: tab.resultVersion,
metadataVersion: tab.metadataVersion,
sortState: tab.sortState
)
return provider
}

private func cacheRowProvider(for tab: QueryTab) {
let provider = makeRowProvider(for: tab)
tabProviderCache[tab.id] = RowProviderCacheEntry(
provider: provider,
providerCache.store(
provider,
for: tab.id,
resultVersion: tab.resultVersion,
metadataVersion: tab.metadataVersion,
sortState: tab.sortState
Expand Down
23 changes: 1 addition & 22 deletions TablePro/Views/Results/DataGridView+RowActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,7 @@ extension TableViewCoordinator {

@MainActor
func setCellValueAtColumn(_ value: String?, at rowIndex: Int, columnIndex: Int) {
guard let tableView = tableView else { return }
guard columnIndex >= 0 && columnIndex < rowProvider.columns.count else { return }

let columnName = rowProvider.columns[columnIndex]
let oldValue = rowProvider.value(atRow: rowIndex, column: columnIndex)
let originalRow = rowProvider.rowValues(at: rowIndex) ?? []

changeManager.recordCellChange(
rowIndex: rowIndex,
columnIndex: columnIndex,
columnName: columnName,
oldValue: oldValue,
newValue: value,
originalRow: originalRow
)

rowProvider.updateValue(value, at: rowIndex, columnIndex: columnIndex)

let tableColumnIndex = columnIndex + 1
tableView.reloadData(
forRowIndexes: IndexSet(integer: rowIndex),
columnIndexes: IndexSet(integer: tableColumnIndex))
commitCellEdit(row: rowIndex, columnIndex: columnIndex, newValue: value)
}

func copyCellValue(at rowIndex: Int, columnIndex: Int) {
Expand Down
8 changes: 1 addition & 7 deletions TablePro/Views/Results/DataGridView+TypePicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@ extension TableViewCoordinator {
currentValue: currentValue,
onCommit: { newValue in
guard let self else { return }
self.commitPopoverEdit(
tableView: tableView,
row: row,
column: column,
columnIndex: columnIndex,
newValue: newValue
)
self.commitPopoverEdit(row: row, columnIndex: columnIndex, newValue: newValue)
},
onDismiss: dismiss
)
Expand Down
35 changes: 35 additions & 0 deletions TablePro/Views/Results/Extensions/DataGridView+CellCommit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// DataGridView+CellCommit.swift
// TablePro
//

import AppKit

extension TableViewCoordinator {
func commitCellEdit(row: Int, columnIndex: Int, newValue: String?) {
guard let tableView else { return }
guard columnIndex >= 0 && columnIndex < rowProvider.columns.count else { return }

let oldValue = rowProvider.value(atRow: row, column: columnIndex)
guard oldValue != newValue else { return }

let columnName = rowProvider.columns[columnIndex]
changeManager.recordCellChange(
rowIndex: row,
columnIndex: columnIndex,
columnName: columnName,
oldValue: oldValue,
newValue: newValue,
originalRow: rowProvider.rowValues(at: row) ?? []
)

rowProvider.updateValue(newValue, at: row, columnIndex: columnIndex)
delegate?.dataGridDidEditCell(row: row, column: columnIndex, newValue: newValue)

let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex)
tableView.reloadData(
forRowIndexes: IndexSet(integer: row),
columnIndexes: IndexSet(integer: tableColumnIndex)
)
Comment on lines +29 to +33
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer table reload until inline edit teardown completes

commitCellEdit now reloads the table synchronously, but control(_:textShouldEndEditing:) calls into this path while AppKit is still in the end-edit callback. Reloading the edited cell at that point can tear down the active field editor mid-cycle (e.g., Enter/Tab commits), which can lead to intermittent dropped commits or broken focus navigation. This path previously deferred reload to the next main-actor turn, and that deferral is still needed for inline text editing.

Useful? React with 👍 / 👎.

}
}
38 changes: 2 additions & 36 deletions TablePro/Views/Results/Extensions/DataGridView+Editing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,7 @@ extension TableViewCoordinator {
}

func commitOverlayEdit(row: Int, columnIndex: Int, newValue: String) {
let oldValue = rowProvider.value(atRow: row, column: columnIndex)
guard oldValue != newValue else { return }

let columnName = rowProvider.columns[columnIndex]
changeManager.recordCellChange(
rowIndex: row,
columnIndex: columnIndex,
columnName: columnName,
oldValue: oldValue,
newValue: newValue,
originalRow: rowProvider.rowValues(at: row) ?? []
)

rowProvider.updateValue(newValue, at: row, columnIndex: columnIndex)
delegate?.dataGridDidEditCell(row: row, column: columnIndex, newValue: newValue)

let tableColumnIndex = DataGridView.tableColumnIndex(for: columnIndex)
tableView?.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: tableColumnIndex))
commitCellEdit(row: row, columnIndex: columnIndex, newValue: newValue)
}

func handleOverlayTabNavigation(row: Int, column: Int, forward: Bool) {
Expand Down Expand Up @@ -159,24 +142,7 @@ extension TableViewCoordinator {
let oldValue = rowProvider.value(atRow: row, column: columnIndex)
let newValue: String? = rawInput.isEmpty && oldValue == nil ? nil : rawInput

guard oldValue != newValue else { return true }

let columnName = rowProvider.columns[columnIndex]
changeManager.recordCellChange(
rowIndex: row,
columnIndex: columnIndex,
columnName: columnName,
oldValue: oldValue,
newValue: newValue,
originalRow: rowProvider.rowValues(at: row) ?? []
)

rowProvider.updateValue(newValue, at: row, columnIndex: columnIndex)
delegate?.dataGridDidEditCell(row: row, column: columnIndex, newValue: newValue)

Task { @MainActor in
tableView.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: column))
}
commitCellEdit(row: row, columnIndex: columnIndex, newValue: newValue)

(control as? CellTextField)?.restoreTruncatedDisplay()

Expand Down
Loading
Loading