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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Running `EXPLAIN` or `EXPLAIN ANALYZE` typed in the editor now opens the plan viewer instead of squashing the plan into one truncated grid cell. (#1480)
- Filtering the data grid keeps you on the keyboard. Applying or clearing a filter returns focus to the grid so you can keep moving through cells, Return applies the filter, and Escape closes the filter panel and returns to the grid. (#1490)
- Opening a table (Return or double-click in the sidebar) moves keyboard focus into the data grid so you can navigate cells with the arrow keys. Arrowing the sidebar still previews tables without taking focus. (#1490)
- Moving a connection into or out of a group now syncs across devices, instead of leaving it ungrouped on your other Macs.
- Opening a table on a connection with many tables no longer stalls for several seconds while autocomplete and table metadata load. Background schema introspection now runs on separate connections instead of waiting behind, or blocking, the query that fills the grid. (#1483)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,9 @@ internal final class MainSplitViewController: NSSplitViewController, InspectorVi
let activeTab = coordinator.tabManager.selectedTab
if activeTab?.tabType == .table, activeTab?.tableContext.tableName == table.name {
coordinator.promotePreviewTab()
coordinator.requestGridFocus()
} else {
coordinator.openTableTab(table, forceNonPreview: true)
coordinator.openTableTab(table, forceNonPreview: true, activateGridFocus: true)
}
},
pendingTruncates: sessionPendingTruncatesBinding,
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Views/Editor/SQLEditorCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ final class SQLEditorCoordinator: TextViewCoordinator, TextViewDelegate {
// Auto-focus: make the editor first responder, then ensure a
// cursor exists. Order matters — setCursorPositions calls
// updateSelectionViews which guards on isFirstResponder.
if let window = textView.window {
if !self.isDestroyed, let window = textView.window {
window.makeFirstResponder(textView)
}
if controller.cursorPositions.isEmpty {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,17 @@ internal extension MainContentCoordinator {
func focusActiveGrid() {
dataTabDelegate?.tableViewCoordinator?.focusGrid()
}

func consumePendingGridFocus() -> Bool {
guard pendingGridFocusOnOpen else { return false }
pendingGridFocusOnOpen = false
return true
}

/// Focus the active grid now if it is already attached, otherwise defer to the grid
/// as it appears. Use for explicit-open gestures where the target grid may or may not
/// be rebuilt (e.g. promoting a preview tab).
func requestGridFocus() {
pendingGridFocusOnOpen = !(dataTabDelegate?.tableViewCoordinator?.focusGrid() ?? false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ extension MainContentCoordinator {
_ table: TableInfo,
showStructure: Bool = false,
redirectToSibling: Bool = false,
forceNonPreview: Bool = false
forceNonPreview: Bool = false,
activateGridFocus: Bool = false
) {
openTableTab(
table.name,
schema: table.schema,
showStructure: showStructure,
isView: table.type == .view,
redirectToSibling: redirectToSibling,
forceNonPreview: forceNonPreview
forceNonPreview: forceNonPreview,
activateGridFocus: activateGridFocus
)
}

Expand All @@ -37,7 +39,8 @@ extension MainContentCoordinator {
showStructure: Bool = false,
isView: Bool = false,
redirectToSibling: Bool = false,
forceNonPreview: Bool = false
forceNonPreview: Bool = false,
activateGridFocus: Bool = false
) {
let navigationModel = PluginMetadataRegistry.shared.snapshot(
forTypeId: connection.type.pluginTypeId
Expand Down Expand Up @@ -65,9 +68,16 @@ extension MainContentCoordinator {
if showStructure, let (_, tabIndex) = tabManager.selectedTabAndIndex {
tabManager.mutate(at: tabIndex) { $0.display.resultsViewMode = .structure }
}
if activateGridFocus {
focusActiveGrid()
}
return
}

if activateGridFocus {
pendingGridFocusOnOpen = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Focus the reused preview grid directly

When preview tabs are enabled and an explicit open reuses the currently selected preview table tab, this only sets pendingGridFocusOnOpen, then reuseActiveTab mutates the same tab in place. The existing DataGridView is not remounted in that path, so KeyHandlingTableView.viewDidMoveToWindow() does not run to consume the pending flag, leaving focus in the sidebar after double-click/Return on a different table in the preview tab. This path needs to focus the attached grid (or otherwise consume the pending intent) after reusing the tab.

Useful? React with 👍 / 👎.

}

// During database switch, update the existing tab in-place instead of
// opening a new native window tab.
if case .loading = SchemaService.shared.state(for: connectionId) {
Expand All @@ -82,6 +92,8 @@ extension MainContentCoordinator {
} catch {
navigationLogger.error("openTableTab addTableTab failed: \(error.localizedDescription, privacy: .public)")
}
} else {
pendingGridFocusOnOpen = false
}
return
}
Expand All @@ -102,6 +114,7 @@ extension MainContentCoordinator {
guard hasMatch,
let windowId = sibling.windowId,
let window = WindowLifecycleMonitor.shared.window(for: windowId) else { continue }
pendingGridFocusOnOpen = false
window.makeKeyAndOrderFront(nil)
Comment on lines +117 to 118

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Focus the sibling grid on redirected opens

When the quick switcher opens a table that already exists in another window, this branch clears the focus request on the current coordinator and only raises the sibling window. Because no grid is attached in the current window and the sibling coordinator is never asked to focus its grid, the sibling window keeps whatever first responder it previously had (for example the editor or sidebar), so arrow keys still do not navigate the selected table after this explicit open.

Useful? React with 👍 / 👎.

return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ extension MainContentCoordinator {
func handleQuickSwitcherSelection(_ item: QuickSwitcherItem) {
switch item.kind {
case .table, .systemTable:
openTableTab(item.name, redirectToSibling: true)
openTableTab(item.name, redirectToSibling: true, activateGridFocus: true)

case .view:
openTableTab(item.name, isView: true, redirectToSibling: true)
openTableTab(item.name, isView: true, redirectToSibling: true, activateGridFocus: true)

case .database:
Task {
Expand Down
4 changes: 4 additions & 0 deletions TablePro/Views/Main/MainContentCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ final class MainContentCoordinator {
/// dispatch insertRows/removeRows directly to the NSTableView via DataGridViewDelegate.
@ObservationIgnored weak var dataTabDelegate: DataTabGridDelegate?

/// One-shot intent set when the user explicitly opens a table (Return/double-click),
/// consumed by the grid as it appears to move focus into it. Never set on mere selection.
@ObservationIgnored var pendingGridFocusOnOpen = false

/// Proxy for toggling the inspector NSSplitViewItem from coordinator code
@ObservationIgnored weak var inspectorProxy: InspectorVisibilityProxy?

Expand Down
6 changes: 4 additions & 2 deletions TablePro/Views/Results/DataGridCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -604,9 +604,11 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
}
}

internal func focusGrid() {
guard let tableView, let window = tableView.window else { return }
@discardableResult
internal func focusGrid() -> Bool {
guard let tableView, let window = tableView.window else { return false }
window.makeFirstResponder(tableView)
return true
}

func beginEditing(displayRow: Int, column: Int) {
Expand Down
8 changes: 8 additions & 0 deletions TablePro/Views/Results/KeyHandlingTableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ final class KeyHandlingTableView: NSTableView {
true
}

override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
guard coordinator?.tabType == .table, let window,
let mainCoordinator = (coordinator?.delegate as? DataTabGridDelegate)?.coordinator,
mainCoordinator.consumePendingGridFocus() else { return }
window.makeFirstResponder(self)
}

override func didAddSubview(_ subview: NSView) {
super.didAddSubview(subview)
guard !isRaisingOverlay else { return }
Expand Down
6 changes: 3 additions & 3 deletions TablePro/Views/Sidebar/DatabaseTreeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ struct DatabaseTreeView: View {
)
} primaryAction: { selection in
guard let ref = selection.first else { return }
openTable(ref.table, in: ref.database, schema: ref.schema)
openTable(ref.table, in: ref.database, schema: ref.schema, activateGridFocus: true)
}
.onExitCommand {
localSelection.removeAll()
Expand Down Expand Up @@ -367,7 +367,7 @@ struct DatabaseTreeView: View {
}
}

private func openTable(_ table: TableInfo, in database: String, schema: String?) {
private func openTable(_ table: TableInfo, in database: String, schema: String?, activateGridFocus: Bool = false) {
Task { @MainActor in
if database != activeDatabase {
await coordinator?.switchDatabase(to: database)
Expand All @@ -377,7 +377,7 @@ struct DatabaseTreeView: View {
PluginManager.shared.supportsSchemaSwitching(for: databaseType) {
await coordinator?.switchSchema(to: schema)
}
coordinator?.openTableTab(table)
coordinator?.openTableTab(table, activateGridFocus: activateGridFocus)
}
}

Expand Down
4 changes: 2 additions & 2 deletions TablePro/Views/Sidebar/FavoritesTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ internal struct FavoritesTabView: View {
@ViewBuilder
private func favoriteTableContextMenu(_ table: TableInfo) -> some View {
Button(String(localized: "Open Table")) {
coordinator?.openTableTab(table)
coordinator?.openTableTab(table, activateGridFocus: true)
}

Button(String(localized: "Show ER Diagram")) {
Expand Down Expand Up @@ -265,7 +265,7 @@ internal struct FavoritesTabView: View {
switch selection {
case .table(let database, let schema, let name):
if let table = favoriteTable(database: database, schema: schema, name: name) {
coordinator?.openTableTab(table)
coordinator?.openTableTab(table, activateGridFocus: true)
}
case .node(let id):
guard let node = viewModel.node(forId: id) else { return }
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Views/Sidebar/SidebarContextMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ struct SidebarContextMenu: View {
Button("Show Structure") {
perform {
if let clickedTable {
coordinator?.openTableTab(clickedTable, showStructure: true)
coordinator?.openTableTab(clickedTable, showStructure: true, activateGridFocus: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not leave grid focus pending for structure tabs

When this path opens the Structure view, the rendered TableStructureView grid is configured without tabType, so KeyHandlingTableView.viewDidMoveToWindow() never consumes pendingGridFocusOnOpen. The flag then stays set until the next data-tab grid appears; for example, after choosing Show Structure, a later single-selection preview open from the sidebar (which passes activateGridFocus: false) can unexpectedly yank focus from the sidebar into that unrelated grid.

Useful? React with 👍 / 👎.

}
}
}
Expand Down
Loading