Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
60f0d95
refactor: Phase 0+1 — ResultSet model + DataGridView split
datlechin Mar 30, 2026
e1d45b6
feat: add keyboard shortcuts for results panel toggle and tab navigation
datlechin Mar 30, 2026
8660455
feat: add ResultsPanelView, ResultTabBar, InlineErrorBanner, ResultSu…
datlechin Mar 30, 2026
28a7540
feat: collapsible results panel with auto-expand on query execution
datlechin Mar 30, 2026
e01a8fc
feat: multi-statement execution produces ResultSet per statement
datlechin Mar 30, 2026
6b5ec45
fix: remove ResultTabBar preview with wrong init params
datlechin Mar 30, 2026
2138a1e
feat: wire ResultTabBar + InlineErrorBanner + ResultSuccessView into …
datlechin Mar 30, 2026
eb3bb04
feat: add Cmd+Shift+W to close result tab, fix stale data on close, g…
datlechin Mar 30, 2026
cf882b7
feat: add toolbar button for toggling results panel
datlechin Mar 30, 2026
fe707a1
fix: group Results + Inspector into ToolbarItemGroup to fix ViewBuild…
datlechin Mar 30, 2026
f816cb4
fix: remove VSplitView animation on results collapse to prevent DataG…
datlechin Mar 30, 2026
87d811c
feat: hide Results toolbar button on table tabs instead of disabling
datlechin Mar 30, 2026
a6356f1
fix: add idealHeight to results section so VSplitView restores proper…
datlechin Mar 30, 2026
69e2604
refactor: replace VSplitView with NSSplitViewController for query tab…
datlechin Mar 30, 2026
c054616
fix: set sizingOptions = [] on NSHostingControllers so panes fill spl…
datlechin Mar 30, 2026
7ca56c6
fix: add frame fill modifiers so SwiftUI content expands in NSSplitVi…
datlechin Mar 30, 2026
6f5ec4a
fix: enable translatesAutoresizingMaskIntoConstraints for NSSplitView…
datlechin Mar 30, 2026
8afee82
fix: wrap NSHostingController in container VC with Auto Layout edge c…
datlechin Mar 30, 2026
8edb0f1
fix: use NSHostingView as pane view so NSSplitView frame drives Swift…
datlechin Mar 30, 2026
8e408f0
fix: embed NSHostingView in container with low compression resistance…
datlechin Mar 30, 2026
f426597
fix: use NSHostingView.sizingOptions = [.minSize] to drop intrinsicCo…
datlechin Mar 30, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Real-time SQL preview with syntax highlighting for CREATE TABLE DDL
- Multi-database CREATE TABLE support: MySQL, PostgreSQL, SQLite, SQL Server, ClickHouse, DuckDB
- Auto-fit column width: double-click column divider, right-click header → "Size to Fit" / "Size All Columns to Fit"
- Close Result Tab shortcut (`Cmd+Shift+W`) to close the active result tab when multiple are open

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions TablePro/Models/Connection/ConnectionToolbarState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ final class ConnectionToolbarState {
/// Whether the current tab is a table tab (enables filter/sort actions)
var isTableTab: Bool = false

/// Whether the results panel is collapsed
var isResultsCollapsed: Bool = false

/// Whether there are pending changes (data grid or file)
var hasPendingChanges: Bool = false

Expand Down
13 changes: 13 additions & 0 deletions TablePro/Models/Query/QueryTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,16 @@ struct QueryTab: Identifiable, Equatable {
// Whether this tab is a preview (temporary) tab that gets replaced on next navigation
var isPreview: Bool

// Multi-result-set support (Phase 0: added alongside existing single-result properties)
var resultSets: [ResultSet] = []
var activeResultSetId: UUID?
var isResultsCollapsed: Bool = false

var activeResultSet: ResultSet? {
guard let id = activeResultSetId else { return resultSets.last }
return resultSets.first { $0.id == id }
}

// Source file URL for .sql files opened from disk (used for deduplication)
var sourceFileURL: URL?

Expand Down Expand Up @@ -542,6 +552,9 @@ struct QueryTab: Identifiable, Equatable {
&& lhs.rowsAffected == rhs.rowsAffected
&& lhs.isPreview == rhs.isPreview
&& lhs.hasUserInteraction == rhs.hasUserInteraction
&& lhs.isResultsCollapsed == rhs.isResultsCollapsed
&& lhs.resultSets.map(\.id) == rhs.resultSets.map(\.id)
&& lhs.activeResultSetId == rhs.activeResultSetId
}
}

Expand Down
46 changes: 46 additions & 0 deletions TablePro/Models/Query/ResultSet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// ResultSet.swift
// TablePro
//
// A single result set from one SQL statement execution.
//

import Foundation
import Observation
import os

@MainActor
@Observable
final class ResultSet: Identifiable {
let id: UUID
var label: String
var rowBuffer: RowBuffer
var executionTime: TimeInterval?
var rowsAffected: Int = 0
var errorMessage: String?
var statusMessage: String?
var tableName: String?
var isEditable: Bool = false
var isPinned: Bool = false
var resultVersion: Int = 0
var metadataVersion: Int = 0
var sortState = SortState()
var pagination = PaginationState()
var columnLayout = ColumnLayoutState()

// Column metadata
var columnTypes: [ColumnType] = []
var columnDefaults: [String: String?] = [:]
var columnForeignKeys: [String: ForeignKeyInfo] = [:]
var columnEnumValues: [String: [String]] = [:]
var columnNullable: [String: Bool] = [:]

var resultColumns: [String] { rowBuffer.columns }
var resultRows: [[String?]] { rowBuffer.rows }

init(id: UUID = UUID(), label: String, rowBuffer: RowBuffer = RowBuffer()) {
self.id = id
self.label = label
self.rowBuffer = rowBuffer
}
}
15 changes: 14 additions & 1 deletion TablePro/Models/UI/KeyboardShortcutModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
case toggleInspector
case toggleFilters
case toggleHistory
case toggleResults
case previousResultTab
case nextResultTab
case closeResultTab

// Tabs
case showPreviousTabBrackets
Expand All @@ -94,7 +98,8 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
.delete, .selectAll, .clearSelection, .addRow,
.duplicateRow, .truncateTable:
return .edit
case .toggleTableBrowser, .toggleInspector, .toggleFilters, .toggleHistory:
case .toggleTableBrowser, .toggleInspector, .toggleFilters, .toggleHistory,
.toggleResults, .previousResultTab, .nextResultTab, .closeResultTab:
return .view
case .showPreviousTabBrackets, .showNextTabBrackets,
.previousTabArrows, .nextTabArrows:
Expand Down Expand Up @@ -137,6 +142,10 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
case .toggleInspector: return String(localized: "Toggle Inspector")
case .toggleFilters: return String(localized: "Toggle Filters")
case .toggleHistory: return String(localized: "Toggle History")
case .toggleResults: return String(localized: "Toggle Results")
case .previousResultTab: return String(localized: "Previous Result")
case .nextResultTab: return String(localized: "Next Result")
case .closeResultTab: return String(localized: "Close Result Tab")
case .showPreviousTabBrackets: return String(localized: "Show Previous Tab")
case .showNextTabBrackets: return String(localized: "Show Next Tab")
case .previousTabArrows: return String(localized: "Previous Tab (Alt)")
Expand Down Expand Up @@ -440,6 +449,10 @@ struct KeyboardSettings: Codable, Equatable {
.toggleInspector: KeyCombo(key: "b", command: true, shift: true),
.toggleFilters: KeyCombo(key: "f", command: true),
.toggleHistory: KeyCombo(key: "y", command: true),
.toggleResults: KeyCombo(key: "r", command: true, option: true),
.previousResultTab: KeyCombo(key: "[", command: true),
.nextResultTab: KeyCombo(key: "]", command: true),
.closeResultTab: KeyCombo(key: "w", command: true, shift: true),

// Tabs
.showPreviousTabBrackets: KeyCombo(key: "[", command: true, shift: true),
Expand Down
Loading
Loading