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 @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- DuckDB VARIANT columns now show their value as text instead of an empty cell.
- A new database group now appears in the connection list right away instead of only after restarting the app. (#1704)
- The SQL formatter keeps nested indentation for UNION, UNION ALL, INTERSECT, and EXCEPT inside a derived table or CTE, and puts the closing parenthesis of a subquery on its own line instead of collapsing it onto the last SELECT. (#1698)
- Toolbar button tooltips now show each action's real keyboard shortcut and follow your custom bindings, instead of a fixed value. The Switch Connection tooltip showed the wrong shortcut. (#1694)

## [0.51.1] - 2026-06-16

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct ConnectionToolbarButton: View {
} label: {
Label("Connection", systemImage: "network")
}
.help(String(localized: "Switch Connection (⌘⌥C)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection))
.popover(isPresented: $coordinator.isConnectionSwitcherShown, arrowEdge: .bottom) {
ConnectionSwitcherPopover()
}
Expand All @@ -37,7 +37,7 @@ struct DatabaseToolbarButton: View {
} label: {
Label(containerName, systemImage: "cylinder")
}
.help(String(format: String(localized: "Open %@ (⌘K)"), containerName))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(format: String(localized: "Open %@"), containerName), for: .openDatabase))
.disabled(
state.connectionState != .connected
|| PluginManager.shared.connectionMode(for: state.databaseType) == .fileBased
Expand Down Expand Up @@ -89,7 +89,7 @@ struct RefreshToolbarButton: View {
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
}
.help(String(localized: "Refresh (⌘R)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Refresh"), for: .refresh))
.disabled(state.connectionState != .connected)
}
}
Expand All @@ -104,7 +104,7 @@ struct SaveChangesToolbarButton: View {
} label: {
Label("Save Changes", systemImage: "checkmark.circle.fill")
}
.help(String(localized: "Save Changes (⌘S)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Save Changes"), for: .saveChanges))
.disabled(
!state.hasPendingChanges
|| state.connectionState != .connected
Expand All @@ -124,7 +124,7 @@ struct QuickSwitcherToolbarButton: View {
} label: {
Label("Quick Switcher", systemImage: "magnifyingglass")
}
.help(String(localized: "Quick Switcher (⇧⌘O)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Quick Switcher"), for: .quickSwitcher))
.disabled(state.connectionState != .connected)
}
}
Expand All @@ -139,7 +139,7 @@ struct NewTabToolbarButton: View {
} label: {
Label("New Tab", systemImage: "plus.rectangle")
}
.help(String(localized: "New Query Tab (⌘T)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "New Query Tab"), for: .newTab))
.disabled(state.connectionState != .connected)
}
}
Expand All @@ -149,13 +149,14 @@ struct PreviewSQLToolbarButton: View {

var body: some View {
let state = coordinator.toolbarState
let langName = PluginManager.shared.queryLanguageName(for: state.databaseType)
let previewLabel = String(format: String(localized: "Preview %@"), langName)
Button {
coordinator.commandActions?.previewSQL()
} label: {
let langName = PluginManager.shared.queryLanguageName(for: state.databaseType)
Label(String(format: String(localized: "Preview %@"), langName), systemImage: "eye")
Label(previewLabel, systemImage: "eye")
}
.help(String(format: String(localized: "Preview %@ (⌘⇧P)"), PluginManager.shared.queryLanguageName(for: state.databaseType)))
.help(AppSettingsManager.shared.keyboard.shortcutHint(previewLabel, for: .previewSQL))
.disabled(!state.hasDataPendingChanges || state.connectionState != .connected)
}
}
Expand All @@ -175,7 +176,7 @@ struct ResultsToolbarButton: View {
: "rectangle.inset.filled"
)
}
.help(String(localized: "Toggle Results (⌘⌥R)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Toggle Results"), for: .toggleResults))
.disabled(state.connectionState != .connected || state.isTableTab)
}
}
Expand Down Expand Up @@ -205,7 +206,7 @@ struct HistoryToolbarButton: View {
} label: {
Label("History", systemImage: "clock")
}
.help(String(localized: "Toggle Query History (⌘Y)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Toggle Query History"), for: .toggleHistory))
}
}

Expand All @@ -219,7 +220,7 @@ struct ExportToolbarButton: View {
} label: {
Label("Export", systemImage: "square.and.arrow.up")
}
.help(String(localized: "Export Data (⌘⇧E)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Export Data"), for: .export))
.disabled(state.connectionState != .connected)
}
}
Expand All @@ -238,7 +239,7 @@ struct ImportToolbarButton: View {
} label: {
Label("Import", systemImage: "square.and.arrow.down")
}
.help(String(localized: "Import Data (⌘⇧I)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Import Data"), for: .importData))
.disabled(isDisabled || formats.isEmpty)
} else {
Menu {
Expand All @@ -250,7 +251,7 @@ struct ImportToolbarButton: View {
} label: {
Label("Import", systemImage: "square.and.arrow.down")
}
.help(String(localized: "Import Data (⌘⇧I)"))
.help(AppSettingsManager.shared.keyboard.shortcutHint(String(localized: "Import Data"), for: .importData))
.disabled(isDisabled)
}
}
Expand Down
9 changes: 9 additions & 0 deletions TablePro/Models/UI/KeyboardShortcutModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,15 @@ struct KeyboardSettings: Codable, Equatable {
return KeyboardShortcut(equivalent, modifiers: key.eventModifiers)
}

/// A tooltip/help string that appends the action's resolved shortcut, e.g.
/// "Switch Connection (⌃⌘C)". Returns just the label when the shortcut is
/// cleared or unset. Reflects user overrides because it resolves through
/// `shortcut(for:)`.
func shortcutHint(_ label: String, for action: ShortcutAction) -> String {
guard let key = shortcut(for: action), !key.isCleared else { return label }
return "\(label) (\(key.displayString))"
}

// MARK: - Default Shortcuts

/// Default shortcuts, applied when the user has no override. An action absent
Expand Down
5 changes: 1 addition & 4 deletions TablePro/Views/Components/PaginationControlsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ struct PaginationControlsView: View {
}

private func helpText(_ label: String, for shortcut: ShortcutAction) -> String {
guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: shortcut), !combo.isCleared else {
return label
}
return "\(label) (\(combo.displayString))"
AppSettingsManager.shared.keyboard.shortcutHint(label, for: shortcut)
}

private var pageIndicator: some View {
Expand Down
5 changes: 1 addition & 4 deletions TablePro/Views/Editor/QueryEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,7 @@ struct QueryEditorView: View {
// MARK: - Helpers

private func shortcutHint(_ label: String, for action: ShortcutAction) -> String {
guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: action), !combo.isCleared else {
return label
}
return "\(label) (\(combo.displayString))"
AppSettingsManager.shared.keyboard.shortcutHint(label, for: action)
}

@ViewBuilder
Expand Down
6 changes: 1 addition & 5 deletions TablePro/Views/Main/Child/MainStatusBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,7 @@ struct MainStatusBarView: View {
}

private func helpText(_ label: String, shortcut action: ShortcutAction) -> String {
guard let combo = AppSettingsManager.shared.keyboard.shortcut(for: action),
!combo.isCleared else {
return label
}
return "\(label) (\(combo.displayString))"
AppSettingsManager.shared.keyboard.shortcutHint(label, for: action)
}

private var columnsAccessibilityLabel: String {
Expand Down
37 changes: 37 additions & 0 deletions TableProTests/Models/KeyboardShortcutTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,40 @@ struct KeyboardSettingsMigrationTests {
#expect(settings.shortcut(for: .executeQuery)?.isCleared == true)
}
}

@Suite("Shortcut hint")
struct ShortcutHintTests {
@Test("Switch Connection default hint shows Control+Command+C")
func switchConnectionDefaultHint() {
let hint = KeyboardSettings.default.shortcutHint(
String(localized: "Switch Connection"),
for: .switchConnection
)
#expect(hint == "Switch Connection (⌃⌘C)")
}

@Test("Hint reflects a user override")
func hintReflectsOverride() {
var settings = KeyboardSettings.default
settings.setShortcut(.character("j", command: true), for: .switchConnection)
let hint = settings.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection)
#expect(hint == "Switch Connection (⌘J)")
}

@Test("Cleared shortcut shows label only")
func clearedShortcutShowsLabelOnly() {
var settings = KeyboardSettings.default
settings.clearShortcut(for: .switchConnection)
let hint = settings.shortcutHint(String(localized: "Switch Connection"), for: .switchConnection)
#expect(hint == "Switch Connection")
}

@Test("Action without a default shows label only")
func unsetShortcutShowsLabelOnly() {
let hint = KeyboardSettings.default.shortcutHint(
String(localized: "Manage Connections"),
for: .manageConnections
)
#expect(hint == "Manage Connections")
}
}
Loading