Skip to content

Commit 7deb5b1

Browse files
authored
refactor: rewrite query results system with clean architecture (#512)
* refactor: Phase 0+1 — ResultSet model + DataGridView split Phase 0: Add ResultSet @observable class and extend QueryTab with resultSets array, activeResultSetId, isResultsCollapsed. Phase 1: Extract TableViewCoordinator from DataGridView.swift into DataGridCoordinator.swift. DataGridView.swift now contains only the NSViewRepresentable shell + helper types. Zero behavior change. * feat: add keyboard shortcuts for results panel toggle and tab navigation - Cmd+Opt+R: Toggle Results panel collapse/expand - Cmd+[: Previous Result tab - Cmd+]: Next Result tab - View menu items + command actions wired - Keyboard shortcuts docs updated * feat: add ResultsPanelView, ResultTabBar, InlineErrorBanner, ResultSuccessView - ResultsPanelView: orchestrates result tab bar, error banner, content area - ResultTabBar: horizontal scrollable tabs with pin/close/close-others - InlineErrorBanner: red dismissable error banner with optional AI fix button - ResultSuccessView: compact DDL/DML success (replaces full-screen QuerySuccessView) * feat: collapsible results panel with auto-expand on query execution - Wrap resultsSection in conditional on isResultsCollapsed in queryTabContent - Smooth animation (200ms easeInOut) when toggling collapse - Auto-expand results when new query results arrive (single + multi-statement) - Table tabs and structure tabs unaffected * feat: multi-statement execution produces ResultSet per statement - executeMultipleStatements builds a ResultSet for each statement with deep-copied rows, column types, execution time, and table name - applyPhase1Result creates a ResultSet sharing the same RowBuffer - Pinned results preserved on re-execution, unpinned replaced - Auto-expand collapsed results panel on new data * fix: remove ResultTabBar preview with wrong init params * feat: wire ResultTabBar + InlineErrorBanner + ResultSuccessView into resultsSection Replace old QuerySuccessView with ResultSuccessView, add result tab bar for multi-result display, add inline error banner. Uses real DataGridView (not placeholder). Structure and Explain paths unchanged. * feat: add Cmd+Shift+W to close result tab, fix stale data on close, guard pinned tabs * feat: add toolbar button for toggling results panel * fix: group Results + Inspector into ToolbarItemGroup to fix ViewBuilder 10-item limit * fix: remove VSplitView animation on results collapse to prevent DataGridView layout gap * feat: hide Results toolbar button on table tabs instead of disabling * fix: add idealHeight to results section so VSplitView restores proper height after collapse * refactor: replace VSplitView with NSSplitViewController for query tab split * fix: set sizingOptions = [] on NSHostingControllers so panes fill split view * fix: add frame fill modifiers so SwiftUI content expands in NSSplitView panes * fix: enable translatesAutoresizingMaskIntoConstraints for NSSplitView frame-based layout * fix: wrap NSHostingController in container VC with Auto Layout edge constraints * fix: use NSHostingView as pane view so NSSplitView frame drives SwiftUI layout * fix: embed NSHostingView in container with low compression resistance for split resize * fix: use NSHostingView.sizingOptions = [.minSize] to drop intrinsicContentSize constraint * refactor: use NSSplitView directly via NSViewRepresentable instead of NSSplitViewController * fix: restore ResultTabBar + InlineErrorBanner in resultsSection, hide divider when collapsed * fix: move divider to bottom edge before hiding pane and force display to clear divider line * fix: read DataGridView data from active ResultSet and invalidate cache on tab switch * fix: don't show ResultSuccessView after all result tabs are closed * fix: clear rowBuffer and bump resultVersion when all result tabs closed * fix: show empty state instead of stale DataGridView when all result tabs closed * feat: auto-collapse results panel when all result tabs are closed * fix: address all review issues for query results rewrite 1. Delete dead code ResultsPanelView.swift (never integrated) 2. Fix sortIndicesForTab to use active ResultSet data instead of tab-level 3. Change Cmd+[/] to Cmd+Opt+[/] to avoid system navigation conflict 4. Fix ResultSuccessView localization (use String(format:localized:)) 5. Extract closeResultSet(id:) to coordinator, remove 3x duplication 6. Toolbar toggle button icon reflects collapsed/expanded state * fix: architecture + UIUX review fixes 1. Pin toggle routes through coordinator + bumps resultVersion for SwiftUI reactivity (was mutating class directly, bypassing Equatable) 2. Remove dead onAIFix API from InlineErrorBanner 3. Use NSColor.selectedControlColor for active tab (was wrong .selectedContentBackgroundColor.opacity(0.3), broken in dark mode) 4. ResultTabBar height 28→32pt (HIG minimum), replace deprecated .cornerRadius with RoundedRectangle, localize context menu strings 5. Consolidate dual ResultSuccessView paths into single branch * docs: update keyboard shortcuts, sql-editor, and CHANGELOG for query results rewrite - Fix Previous/Next Result shortcuts: Cmd+[ → Cmd+Opt+[ (matches code) - Add sql-editor.mdx sections: collapsible panel, multi-result tabs, pinning, inline errors, non-SELECT success view - Expand CHANGELOG [Unreleased] with all PR #512 features * docs: simplify CHANGELOG entries
1 parent 7952527 commit 7deb5b1

23 files changed

Lines changed: 1240 additions & 427 deletions

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12-
- Visual Create Table UI with column, index, and foreign key editors (sidebar → "Create New Table...")
13-
- Real-time SQL preview with syntax highlighting for CREATE TABLE DDL
14-
- Multi-database CREATE TABLE support: MySQL, PostgreSQL, SQLite, SQL Server, ClickHouse, DuckDB
15-
- Auto-fit column width: double-click column divider, right-click header → "Size to Fit" / "Size All Columns to Fit"
12+
- Visual Create Table UI with multi-database support (sidebar → "Create New Table...")
13+
- Auto-fit column width: double-click column divider or right-click → "Size to Fit"
14+
- Collapsible results panel (`Cmd+Opt+R`), multiple result tabs for multi-statement queries, result pinning
15+
- Inline error banner for query errors
1616

1717
### Fixed
1818

TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

TablePro/Models/Connection/ConnectionToolbarState.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ final class ConnectionToolbarState {
174174
/// Whether the current tab is a table tab (enables filter/sort actions)
175175
var isTableTab: Bool = false
176176

177+
/// Whether the results panel is collapsed
178+
var isResultsCollapsed: Bool = false
179+
177180
/// Whether there are pending changes (data grid or file)
178181
var hasPendingChanges: Bool = false
179182

TablePro/Models/Query/QueryTab.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,16 @@ struct QueryTab: Identifiable, Equatable {
369369
// Whether this tab is a preview (temporary) tab that gets replaced on next navigation
370370
var isPreview: Bool
371371

372+
// Multi-result-set support (Phase 0: added alongside existing single-result properties)
373+
var resultSets: [ResultSet] = []
374+
var activeResultSetId: UUID?
375+
var isResultsCollapsed: Bool = false
376+
377+
var activeResultSet: ResultSet? {
378+
guard let id = activeResultSetId else { return resultSets.last }
379+
return resultSets.first { $0.id == id }
380+
}
381+
372382
// Source file URL for .sql files opened from disk (used for deduplication)
373383
var sourceFileURL: URL?
374384

@@ -542,6 +552,9 @@ struct QueryTab: Identifiable, Equatable {
542552
&& lhs.rowsAffected == rhs.rowsAffected
543553
&& lhs.isPreview == rhs.isPreview
544554
&& lhs.hasUserInteraction == rhs.hasUserInteraction
555+
&& lhs.isResultsCollapsed == rhs.isResultsCollapsed
556+
&& lhs.resultSets.map(\.id) == rhs.resultSets.map(\.id)
557+
&& lhs.activeResultSetId == rhs.activeResultSetId
545558
}
546559
}
547560

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// ResultSet.swift
3+
// TablePro
4+
//
5+
// A single result set from one SQL statement execution.
6+
//
7+
8+
import Foundation
9+
import Observation
10+
import os
11+
12+
@MainActor
13+
@Observable
14+
final class ResultSet: Identifiable {
15+
let id: UUID
16+
var label: String
17+
var rowBuffer: RowBuffer
18+
var executionTime: TimeInterval?
19+
var rowsAffected: Int = 0
20+
var errorMessage: String?
21+
var statusMessage: String?
22+
var tableName: String?
23+
var isEditable: Bool = false
24+
var isPinned: Bool = false
25+
var resultVersion: Int = 0
26+
var metadataVersion: Int = 0
27+
var sortState = SortState()
28+
var pagination = PaginationState()
29+
var columnLayout = ColumnLayoutState()
30+
31+
// Column metadata
32+
var columnTypes: [ColumnType] = []
33+
var columnDefaults: [String: String?] = [:]
34+
var columnForeignKeys: [String: ForeignKeyInfo] = [:]
35+
var columnEnumValues: [String: [String]] = [:]
36+
var columnNullable: [String: Bool] = [:]
37+
38+
var resultColumns: [String] { rowBuffer.columns }
39+
var resultRows: [[String?]] { rowBuffer.rows }
40+
41+
init(id: UUID = UUID(), label: String, rowBuffer: RowBuffer = RowBuffer()) {
42+
self.id = id
43+
self.label = label
44+
self.rowBuffer = rowBuffer
45+
}
46+
}

TablePro/Models/UI/KeyboardShortcutModels.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
7171
case toggleInspector
7272
case toggleFilters
7373
case toggleHistory
74+
case toggleResults
75+
case previousResultTab
76+
case nextResultTab
77+
case closeResultTab
7478

7579
// Tabs
7680
case showPreviousTabBrackets
@@ -94,7 +98,8 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
9498
.delete, .selectAll, .clearSelection, .addRow,
9599
.duplicateRow, .truncateTable:
96100
return .edit
97-
case .toggleTableBrowser, .toggleInspector, .toggleFilters, .toggleHistory:
101+
case .toggleTableBrowser, .toggleInspector, .toggleFilters, .toggleHistory,
102+
.toggleResults, .previousResultTab, .nextResultTab, .closeResultTab:
98103
return .view
99104
case .showPreviousTabBrackets, .showNextTabBrackets,
100105
.previousTabArrows, .nextTabArrows:
@@ -137,6 +142,10 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
137142
case .toggleInspector: return String(localized: "Toggle Inspector")
138143
case .toggleFilters: return String(localized: "Toggle Filters")
139144
case .toggleHistory: return String(localized: "Toggle History")
145+
case .toggleResults: return String(localized: "Toggle Results")
146+
case .previousResultTab: return String(localized: "Previous Result")
147+
case .nextResultTab: return String(localized: "Next Result")
148+
case .closeResultTab: return String(localized: "Close Result Tab")
140149
case .showPreviousTabBrackets: return String(localized: "Show Previous Tab")
141150
case .showNextTabBrackets: return String(localized: "Show Next Tab")
142151
case .previousTabArrows: return String(localized: "Previous Tab (Alt)")
@@ -440,6 +449,10 @@ struct KeyboardSettings: Codable, Equatable {
440449
.toggleInspector: KeyCombo(key: "b", command: true, shift: true),
441450
.toggleFilters: KeyCombo(key: "f", command: true),
442451
.toggleHistory: KeyCombo(key: "y", command: true),
452+
.toggleResults: KeyCombo(key: "r", command: true, option: true),
453+
.previousResultTab: KeyCombo(key: "[", command: true, option: true),
454+
.nextResultTab: KeyCombo(key: "]", command: true, option: true),
455+
.closeResultTab: KeyCombo(key: "w", command: true, shift: true),
443456

444457
// Tabs
445458
.showPreviousTabBrackets: KeyCombo(key: "[", command: true, shift: true),

0 commit comments

Comments
 (0)