Skip to content
Closed
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Copy rows writes TSV, HTML table, and plain text to the clipboard for richer paste in spreadsheet apps
- Row drag adds TSV and HTML representations alongside the internal drag type
- AI provider settings allow manually entering a model name when the provider does not return one
- Edit menu has a Find submenu with Find, Find Next (`Cmd+G`), Find Previous (`Cmd+Shift+G`), Use Selection for Find (`Cmd+E`), and Jump to Selection (`Cmd+J`)
- File menu has a New Window item (`Cmd+Ctrl+N`) that opens a fresh editor window for the active connection

### Changed

- `Cmd+N` now opens the New Connection form. Manage Connections moves to the File menu without a default shortcut.
- `Cmd+D` now duplicates the selected row. Save as Favorite moves to `Cmd+Shift+D`.
- Removed default shortcuts that conflicted with system reservations: `Cmd+Y` (Quick Look), `Cmd+Option+Delete` (Empty Trash), `Cmd+Ctrl+C` (Color Picker), `Cmd+L` (URL bar / Add Link).
- Show History moves from `Cmd+Y` (system Quick Look) to `Cmd+Shift+Y` to free the system shortcut while keeping the Y mnemonic. (`Cmd+Option+H` is reserved by macOS for Hide Others, so we avoid that chord too.)
- Truncate Table no longer has a default shortcut; the action stays in the Edit menu and the sidebar context menu.
- Switch Connection no longer has a default shortcut; the menu entry remains and users can rebind in Settings > Keyboard.
- Explain with AI no longer has a default shortcut; the menu entry remains and users can rebind in Settings > Keyboard.
- File menu "Save Changes" renamed to "Save".
- View menu Show/Hide labels now flip based on panel state (Show Sidebar / Hide Sidebar, Show Inspector / Hide Inspector, Show Filters / Hide Filters, Show History / Hide History, Show Results / Hide Results).
- MCP server lazy-starts on first external request. Manual enable in Settings is no longer required
- Settings tab renamed from "MCP" to "Integrations" with new sections for connected clients, activity log, and pairing
- Integrations settings: rename MCP Server section to Integrations, restructure with searchable activity log, native list with keyboard navigation, accessibility labels, color-blind-safe status icons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,16 @@ public extension TextViewController {
_ = textView.resignFirstResponder()
findViewController?.showFindPanel()
}

func findNextMatch() {
findViewController?.viewModel.moveToNextMatch()
}

func findPreviousMatch() {
findViewController?.viewModel.moveToPreviousMatch()
}

func setFindText(_ text: String) {
findViewController?.viewModel.findText = text
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ private struct HistoryToolbarButton: View {
} label: {
Label("History", systemImage: "clock")
}
.help(String(localized: "Toggle Query History (⌘Y)"))
.help(String(localized: "Toggle Query History (⌘Y)"))
}
}

Expand Down
52 changes: 35 additions & 17 deletions TablePro/Models/UI/KeyboardShortcutModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ enum ShortcutCategory: String, Codable, CaseIterable, Identifiable {
/// All customizable keyboard shortcut actions
enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
// File
case newConnection
case newWindow
case manageConnections
case newTab
case openDatabase
Expand Down Expand Up @@ -70,6 +72,11 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
case delete
case selectAll
case clearSelection
case find
case findNext
case findPrevious
case useSelectionForFind
case jumpToSelection
case addRow
case duplicateRow
case truncateTable
Expand Down Expand Up @@ -98,14 +105,15 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {

var category: ShortcutCategory {
switch self {
case .manageConnections, .newTab, .openDatabase, .openFile, .switchConnection,
.saveChanges, .saveAs, .previewSQL, .closeTab, .refresh,
case .newConnection, .newWindow, .manageConnections, .newTab, .openDatabase, .openFile,
.switchConnection, .saveChanges, .saveAs, .previewSQL, .closeTab, .refresh,
.executeQuery, .explainQuery, .formatQuery, .export, .importData, .quickSwitcher,
.previousPage, .nextPage, .saveAsFavorite, .openTerminal:
return .file
case .undo, .redo, .cut, .copy, .copyWithHeaders, .copyAsJson, .paste,
.delete, .selectAll, .clearSelection, .addRow,
.duplicateRow, .truncateTable, .previewFKReference:
.delete, .selectAll, .clearSelection,
.find, .findNext, .findPrevious, .useSelectionForFind, .jumpToSelection,
.addRow, .duplicateRow, .truncateTable, .previewFKReference:
return .edit
case .toggleTableBrowser, .toggleInspector, .toggleFilters, .toggleHistory,
.toggleResults, .previousResultTab, .nextResultTab, .closeResultTab:
Expand All @@ -119,13 +127,15 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {

var displayName: String {
switch self {
case .newConnection: return String(localized: "New Connection")
case .newWindow: return String(localized: "New Window")
case .manageConnections: return String(localized: "Manage Connections")
case .executeQuery: return String(localized: "Execute Query")
case .newTab: return String(localized: "New Tab")
case .openDatabase: return String(localized: "Open Database")
case .openFile: return String(localized: "Open File")
case .switchConnection: return String(localized: "Switch Connection")
case .saveChanges: return String(localized: "Save Changes")
case .saveChanges: return String(localized: "Save")
case .saveAs: return String(localized: "Save As")
case .previewSQL: return String(localized: "Preview SQL")
case .closeTab: return String(localized: "Close Tab")
Expand All @@ -148,16 +158,21 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
case .delete: return String(localized: "Delete")
case .selectAll: return String(localized: "Select All")
case .clearSelection: return String(localized: "Clear Selection")
case .find: return String(localized: "Find")
case .findNext: return String(localized: "Find Next")
case .findPrevious: return String(localized: "Find Previous")
case .useSelectionForFind: return String(localized: "Use Selection for Find")
case .jumpToSelection: return String(localized: "Jump to Selection")
case .addRow: return String(localized: "Add Row")
case .duplicateRow: return String(localized: "Duplicate Row")
case .truncateTable: return String(localized: "Truncate Table")
case .previewFKReference: return String(localized: "Preview FK Reference")
case .saveAsFavorite: return String(localized: "Save as Favorite")
case .toggleTableBrowser: return String(localized: "Toggle Table Browser")
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 .toggleTableBrowser: return String(localized: "Show Sidebar")
case .toggleInspector: return String(localized: "Show Inspector")
case .toggleFilters: return String(localized: "Show Filters")
case .toggleHistory: return String(localized: "Show History")
case .toggleResults: return String(localized: "Show Results")
case .previousResultTab: return String(localized: "Previous Result")
case .nextResultTab: return String(localized: "Next Result")
case .closeResultTab: return String(localized: "Close Result Tab")
Expand Down Expand Up @@ -457,12 +472,12 @@ struct KeyboardSettings: Codable, Equatable {
/// Default shortcuts — applied when user has no overrides
static let defaultShortcuts: [ShortcutAction: KeyCombo] = [
// File
.manageConnections: KeyCombo(key: "n", command: true),
.newConnection: KeyCombo(key: "n", command: true),
.newWindow: KeyCombo(key: "n", command: true, control: true),
.executeQuery: KeyCombo(key: "return", command: true, isSpecialKey: true),
.newTab: KeyCombo(key: "t", command: true),
.openDatabase: KeyCombo(key: "k", command: true),
.openFile: KeyCombo(key: "o", command: true),
.switchConnection: KeyCombo(key: "c", command: true, control: true),
.saveChanges: KeyCombo(key: "s", command: true),
.saveAs: KeyCombo(key: "s", command: true, shift: true),
.previewSQL: KeyCombo(key: "p", command: true, shift: true),
Expand All @@ -488,17 +503,21 @@ struct KeyboardSettings: Codable, Equatable {
.delete: KeyCombo(key: "delete", command: true, isSpecialKey: true),
.selectAll: KeyCombo(key: "a", command: true),
.clearSelection: KeyCombo(key: "escape", isSpecialKey: true),
.find: KeyCombo(key: "f", command: true),
.findNext: KeyCombo(key: "g", command: true),
.findPrevious: KeyCombo(key: "g", command: true, shift: true),
.useSelectionForFind: KeyCombo(key: "e", command: true),
.jumpToSelection: KeyCombo(key: "j", command: true),
.addRow: KeyCombo(key: "n", command: true, shift: true),
.duplicateRow: KeyCombo(key: "d", command: true, shift: true),
.truncateTable: KeyCombo(key: "delete", option: true, isSpecialKey: true),
.duplicateRow: KeyCombo(key: "d", command: true),
.previewFKReference: KeyCombo(key: "space", isSpecialKey: true),
.saveAsFavorite: KeyCombo(key: "d", command: true),
.saveAsFavorite: KeyCombo(key: "d", command: true, shift: true),

// View
.toggleTableBrowser: KeyCombo(key: "0", command: true),
.toggleInspector: KeyCombo(key: "i", command: true, option: true),
.toggleFilters: KeyCombo(key: "f", command: true, shift: true),
.toggleHistory: KeyCombo(key: "y", command: true),
.toggleHistory: KeyCombo(key: "y", command: true, shift: true),
.toggleResults: KeyCombo(key: "r", command: true, option: true),
.previousResultTab: KeyCombo(key: "[", command: true, option: true),
.nextResultTab: KeyCombo(key: "]", command: true, option: true),
Expand All @@ -509,7 +528,6 @@ struct KeyboardSettings: Codable, Equatable {
.showNextTab: KeyCombo(key: "]", command: true, shift: true),

// AI
.aiExplainQuery: KeyCombo(key: "l", command: true),
.aiOptimizeQuery: KeyCombo(key: "l", command: true, option: true),
]
}
Expand Down
82 changes: 69 additions & 13 deletions TablePro/TableProApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ struct AppMenuCommands: Commands {
settingsManager.keyboard.keyboardShortcut(for: action)
}

private func openNewMainWindow() {
let connectionId = actions?.connectionId
?? NSApp.keyWindow.flatMap { WindowLifecycleMonitor.shared.connectionId(forWindow: $0) }
guard let connectionId else {
WelcomeWindowFactory.openOrFront()
return
}
WindowManager.shared.openTab(
payload: EditorTabPayload(connectionId: connectionId, intent: .newEmptyTab)
)
}

/// Prefers the focused scene value; falls back to the coordinator back-reference
/// so Cmd+W still routes through `closeTab()` (with its unsaved-changes dialog)
/// when focus is inside an AppKit subview and `@FocusedValue` has not resolved.
Expand Down Expand Up @@ -195,10 +207,15 @@ struct AppMenuCommands: Commands {

// File menu
CommandGroup(replacing: .newItem) {
Button("Manage Connections") {
WelcomeWindowFactory.openOrFront()
Button(String(localized: "New Connection...")) {
ConnectionFormWindowFactory.openOrFront()
}
.optionalKeyboardShortcut(shortcut(for: .manageConnections))
.optionalKeyboardShortcut(shortcut(for: .newConnection))

Button(String(localized: "New Window")) {
openNewMainWindow()
}
.optionalKeyboardShortcut(shortcut(for: .newWindow))
}

CommandGroup(after: .newItem) {
Expand All @@ -213,6 +230,11 @@ struct AppMenuCommands: Commands {
}
.disabled(!(actions?.isConnected ?? false) || actions?.isReadOnly ?? false)

Button(String(localized: "Manage Connections...")) {
WelcomeWindowFactory.openOrFront()
}
.optionalKeyboardShortcut(shortcut(for: .manageConnections))

Button("Open Database...") {
actions?.openDatabaseSwitcher()
}
Expand All @@ -227,7 +249,7 @@ struct AppMenuCommands: Commands {

Divider()

Button("Save Changes") {
Button(String(localized: "Save")) {
actions?.saveChanges()
}
.optionalKeyboardShortcut(shortcut(for: .saveChanges))
Expand Down Expand Up @@ -423,14 +445,38 @@ struct AppMenuCommands: Commands {
// Edit menu - pasteboard commands with FocusedValue support
PasteboardCommands(settingsManager: settingsManager)

// Edit menu - Find + row operations (after pasteboard)
// Edit menu - Find submenu + row operations (after pasteboard)
CommandGroup(after: .pasteboard) {
Divider()

Button(String(localized: "Find...")) {
EditorEventRouter.shared.showFindPanelForKeyWindow()
Menu(String(localized: "Find")) {
Button(String(localized: "Find...")) {
EditorEventRouter.shared.showFindPanelForKeyWindow()
}
.optionalKeyboardShortcut(shortcut(for: .find))

Button(String(localized: "Find Next")) {
EditorEventRouter.shared.findNextForKeyWindow()
}
.optionalKeyboardShortcut(shortcut(for: .findNext))

Button(String(localized: "Find Previous")) {
EditorEventRouter.shared.findPreviousForKeyWindow()
}
.optionalKeyboardShortcut(shortcut(for: .findPrevious))

Divider()

Button(String(localized: "Use Selection for Find")) {
EditorEventRouter.shared.useSelectionForFindForKeyWindow()
}
.optionalKeyboardShortcut(shortcut(for: .useSelectionForFind))

Button(String(localized: "Jump to Selection")) {
EditorEventRouter.shared.jumpToSelectionForKeyWindow()
}
.optionalKeyboardShortcut(shortcut(for: .jumpToSelection))
}
.keyboardShortcut("f", modifiers: .command)

Divider()

Expand Down Expand Up @@ -458,34 +504,44 @@ struct AppMenuCommands: Commands {

// View menu
CommandGroup(after: .sidebar) {
Button(String(localized: "Toggle Sidebar")) {
Button(actions?.isSidebarVisible == true
? String(localized: "Hide Sidebar")
: String(localized: "Show Sidebar")) {
NSApp.sendAction(#selector(NSSplitViewController.toggleSidebar(_:)), to: nil, from: nil)
}
.optionalKeyboardShortcut(shortcut(for: .toggleTableBrowser))

Button("Toggle Inspector") {
Button(actions?.isInspectorVisible == true
? String(localized: "Hide Inspector")
: String(localized: "Show Inspector")) {
actions?.toggleRightSidebar()
}
.optionalKeyboardShortcut(shortcut(for: .toggleInspector))
.disabled(!(actions?.isConnected ?? false))

Divider()

Button("Toggle Filters") {
Button(actions?.isFilterPanelVisible == true
? String(localized: "Hide Filters")
: String(localized: "Show Filters")) {
actions?.toggleFilterPanel()
}
.optionalKeyboardShortcut(shortcut(for: .toggleFilters))
.disabled(!(actions?.isConnected ?? false) || !(actions?.isTableTab ?? false))

Button("Toggle History") {
Button(actions?.isHistoryPanelVisible == true
? String(localized: "Hide History")
: String(localized: "Show History")) {
actions?.toggleHistoryPanel()
}
.optionalKeyboardShortcut(shortcut(for: .toggleHistory))
.disabled(!(actions?.isConnected ?? false))

Divider()

Button("Toggle Results") {
Button(actions?.isResultsVisible == true
? String(localized: "Hide Results")
: String(localized: "Show Results")) {
actions?.toggleResults()
}
.optionalKeyboardShortcut(shortcut(for: .toggleResults))
Expand Down
20 changes: 20 additions & 0 deletions TablePro/Views/Editor/EditorEventRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@ internal final class EditorEventRouter {
coordinator.showFindPanel()
}

internal func findNextForKeyWindow() {
guard let (coordinator, _) = editor(for: NSApp.keyWindow) else { return }
coordinator.findNextMatch()
}

internal func findPreviousForKeyWindow() {
guard let (coordinator, _) = editor(for: NSApp.keyWindow) else { return }
coordinator.findPreviousMatch()
}

internal func useSelectionForFindForKeyWindow() {
guard let (coordinator, _) = editor(for: NSApp.keyWindow) else { return }
coordinator.useSelectionForFind()
}

internal func jumpToSelectionForKeyWindow() {
guard let (coordinator, _) = editor(for: NSApp.keyWindow) else { return }
coordinator.jumpToSelection()
}

// MARK: - Lookup

private func editor(for window: NSWindow?) -> (SQLEditorCoordinator, TextView)? {
Expand Down
25 changes: 25 additions & 0 deletions TablePro/Views/Editor/SQLEditorCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,31 @@ final class SQLEditorCoordinator: TextViewCoordinator, TextViewDelegate {
controller?.showFindPanel()
}

func findNextMatch() {
controller?.findNextMatch()
}

func findPreviousMatch() {
controller?.findPreviousMatch()
}

func useSelectionForFind() {
guard let controller, let textView = controller.textView else { return }
let range = textView.selectedRange()
guard range.length > 0 else { return }
let selected = (textView.string as NSString).substring(with: range)
guard !selected.isEmpty else { return }
controller.setFindText(selected)
controller.showFindPanel()
}

func jumpToSelection() {
guard let controller, let textView = controller.textView else { return }
let range = textView.selectedRange()
guard range.location != NSNotFound else { return }
textView.scrollToRange(range)
}

// MARK: - CodeEditSourceEditor Workarounds

/// Reorder FindViewController's subviews so the find panel is on top for hit testing.
Expand Down
Loading
Loading