From 3e31061b66dc89ad2331b20c5c65249292ebdc9c Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Wed, 20 May 2026 21:41:24 +0700 Subject: [PATCH 1/2] fix(datagrid): drive Cmd+F filter toggle from tab context and the View menu --- CHANGELOG.md | 2 +- .../Models/UI/KeyboardShortcutModels.swift | 1 + TablePro/TableProApp.swift | 34 ++++++++++++++----- docs/features/filtering.mdx | 2 +- docs/features/keyboard-shortcuts.mdx | 4 +-- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dee8192c3..b46a4c98f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `Cmd+C` now copies the focused cell value when one row is selected and a cell has focus; with multiple rows selected, or when no cell is focused, it still copies row(s) as TSV. `Cmd+Shift+C` now always copies row(s) as TSV. "Copy with Headers" stays in the Edit menu and row context menu without a default shortcut (#1332) -- `Cmd+F` toggles the data-grid filter panel when the grid is focused; the SQL editor's Find panel still opens with `Cmd+F` when the editor is focused. The old `Cmd+Shift+F` shortcut for filters is removed +- `Cmd+F` toggles the filter panel when viewing a table, and opens the Find panel in the SQL editor. The old `Cmd+Shift+F` shortcut for filters is removed ### Fixed diff --git a/TablePro/Models/UI/KeyboardShortcutModels.swift b/TablePro/Models/UI/KeyboardShortcutModels.swift index 11605e159..2aaa58709 100644 --- a/TablePro/Models/UI/KeyboardShortcutModels.swift +++ b/TablePro/Models/UI/KeyboardShortcutModels.swift @@ -499,6 +499,7 @@ struct KeyboardSettings: Codable, Equatable { // View .toggleTableBrowser: KeyCombo(key: "0", command: true), .toggleInspector: KeyCombo(key: "i", command: true, option: 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, option: true), diff --git a/TablePro/TableProApp.swift b/TablePro/TableProApp.swift index b22c6add7..169b9e1c0 100644 --- a/TablePro/TableProApp.swift +++ b/TablePro/TableProApp.swift @@ -152,6 +152,23 @@ struct AppMenuCommands: Commands { NSApp.keyWindow?.windowController is InspectorWindowController } + /// Where `Cmd+F` resolves in the current context. The data-grid filter lives + /// in the View menu and the editor's Find lives in the Edit menu. Only the + /// item matching the current route binds `Cmd+F`; the other drops it. Two + /// items sharing one key equivalent makes SwiftUI dedupe the shortcut and + /// AppKit bind it to the disabled item, so the live owner must be unique. + private enum CommandFRoute { + case inspectorFilter + case tableFilter + case editorFind + } + + private var commandFRoute: CommandFRoute { + if keyWindowIsInspector { return .inspectorFilter } + if actions?.isTableTab == true { return .tableFilter } + return .editorFind + } + var body: some Commands { // Custom About window + Check for Updates + MCP status CommandGroup(replacing: .appInfo) { @@ -468,16 +485,17 @@ struct AppMenuCommands: Commands { Divider() Button(String(localized: "Find...")) { - if keyWindowIsInspector { + switch commandFRoute { + case .inspectorFilter: NSApp.sendAction(#selector(InspectorViewController.toggleInspectorFilter(_:)), to: nil, from: nil) - } else if NSApp.keyWindow?.firstResponder is KeyHandlingTableView, - actions?.isTableTab == true { - actions?.toggleFilterPanel() - } else { + case .editorFind: EditorEventRouter.shared.showFindPanelForKeyWindow() + case .tableFilter: + break } } - .keyboardShortcut("f", modifiers: .command) + .optionalKeyboardShortcut(commandFRoute == .tableFilter ? nil : KeyboardShortcut("f", modifiers: .command)) + .disabled(commandFRoute == .tableFilter) Divider() @@ -521,8 +539,8 @@ struct AppMenuCommands: Commands { Button("Toggle Filters") { actions?.toggleFilterPanel() } - .optionalKeyboardShortcut(shortcut(for: .toggleFilters)) - .disabled(!(actions?.isConnected ?? false) || !(actions?.isTableTab ?? false)) + .optionalKeyboardShortcut(commandFRoute == .tableFilter ? shortcut(for: .toggleFilters) : nil) + .disabled(commandFRoute != .tableFilter || !(actions?.isConnected ?? false)) Button("Toggle History") { actions?.toggleHistoryPanel() diff --git a/docs/features/filtering.mdx b/docs/features/filtering.mdx index 9bcbff9b6..5d4c1456b 100644 --- a/docs/features/filtering.mdx +++ b/docs/features/filtering.mdx @@ -5,7 +5,7 @@ description: Filter table data with 18 operators, raw SQL, and saved presets # Filtering -Press `Cmd+F` with the data grid focused to open the filter panel. Type a raw SQL WHERE clause and press `Enter` to filter. Raw SQL is the default mode. +Press `Cmd+F` while viewing a table to open the filter panel. Type a raw SQL WHERE clause and press `Enter` to filter. Raw SQL is the default mode. Each row has a column picker, operator, value field, and **+**/**−** buttons. Multiple rows combine with **AND** or **OR** (toggle in the header). Click **Apply** or press `Enter` to activate. Click **Unset** to clear all. diff --git a/docs/features/keyboard-shortcuts.mdx b/docs/features/keyboard-shortcuts.mdx index 11aa941b1..9ca020369 100644 --- a/docs/features/keyboard-shortcuts.mdx +++ b/docs/features/keyboard-shortcuts.mdx @@ -71,7 +71,7 @@ TablePro is keyboard-driven. Most actions have shortcuts, and most menu shortcut | Action | Shortcut | |--------|----------| -| Find (SQL editor focused) | `Cmd+F` | +| Find (in SQL editor) | `Cmd+F` | | Find and replace | `Cmd+Option+F` | | Find next | `Cmd+G` | | Find previous | `Cmd+Shift+G` | @@ -255,7 +255,7 @@ Vim keys only apply in the SQL editor. The data grid and other panels keep their | Action | Shortcut | |--------|----------| -| Toggle filter panel (data grid focused) | `Cmd+F` | +| Toggle filter panel (table tab) | `Cmd+F` | | Apply filters | `Enter` (in value field) | ## Table Structure From 568b9cbcd02513c3f24ab889d0ae598f08103e37 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Wed, 20 May 2026 21:49:41 +0700 Subject: [PATCH 2/2] refactor(datagrid): extract Cmd+F route into a testable type --- TablePro/TableProApp.swift | 32 +++++++++++--------- TableProTests/Views/CommandFRouteTests.swift | 29 ++++++++++++++++++ docs/features/keyboard-shortcuts.mdx | 2 +- 3 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 TableProTests/Views/CommandFRouteTests.swift diff --git a/TablePro/TableProApp.swift b/TablePro/TableProApp.swift index 169b9e1c0..6d2d4b5b3 100644 --- a/TablePro/TableProApp.swift +++ b/TablePro/TableProApp.swift @@ -106,6 +106,23 @@ struct PasteboardCommands: Commands { // MARK: - App Menu Commands +/// Where `Cmd+F` resolves in the current context. The data-grid filter lives in +/// the View menu and the editor's Find lives in the Edit menu. Only the item +/// matching the current route binds `Cmd+F`; the other drops it. Two items +/// sharing one key equivalent makes SwiftUI dedupe the shortcut and AppKit bind +/// it to the disabled item, so the live owner must be unique. +enum CommandFRoute { + case inspectorFilter + case tableFilter + case editorFind + + static func resolve(isInspector: Bool, isTableTab: Bool) -> CommandFRoute { + if isInspector { return .inspectorFilter } + if isTableTab { return .tableFilter } + return .editorFind + } +} + /// All menu commands extracted into a separate Commands struct so that AppState /// changes only re-evaluate the menu items — NOT the Scene body / WindowGroups. struct AppMenuCommands: Commands { @@ -152,21 +169,8 @@ struct AppMenuCommands: Commands { NSApp.keyWindow?.windowController is InspectorWindowController } - /// Where `Cmd+F` resolves in the current context. The data-grid filter lives - /// in the View menu and the editor's Find lives in the Edit menu. Only the - /// item matching the current route binds `Cmd+F`; the other drops it. Two - /// items sharing one key equivalent makes SwiftUI dedupe the shortcut and - /// AppKit bind it to the disabled item, so the live owner must be unique. - private enum CommandFRoute { - case inspectorFilter - case tableFilter - case editorFind - } - private var commandFRoute: CommandFRoute { - if keyWindowIsInspector { return .inspectorFilter } - if actions?.isTableTab == true { return .tableFilter } - return .editorFind + CommandFRoute.resolve(isInspector: keyWindowIsInspector, isTableTab: actions?.isTableTab == true) } var body: some Commands { diff --git a/TableProTests/Views/CommandFRouteTests.swift b/TableProTests/Views/CommandFRouteTests.swift new file mode 100644 index 000000000..dcc70ea8f --- /dev/null +++ b/TableProTests/Views/CommandFRouteTests.swift @@ -0,0 +1,29 @@ +// +// CommandFRouteTests.swift +// TableProTests +// +// Tests for CommandFRoute, which decides where Cmd+F resolves so exactly one +// menu item owns the shortcut per context. +// + +@testable import TablePro +import Testing + +@Suite("CommandFRouteTests") +struct CommandFRouteTests { + @Test("Inspector window takes priority over tab type") + func inspectorWins() { + #expect(CommandFRoute.resolve(isInspector: true, isTableTab: true) == .inspectorFilter) + #expect(CommandFRoute.resolve(isInspector: true, isTableTab: false) == .inspectorFilter) + } + + @Test("Table tab routes to the View-menu filter toggle") + func tableTabFilters() { + #expect(CommandFRoute.resolve(isInspector: false, isTableTab: true) == .tableFilter) + } + + @Test("Everything else routes to the editor Find panel") + func otherwiseFinds() { + #expect(CommandFRoute.resolve(isInspector: false, isTableTab: false) == .editorFind) + } +} diff --git a/docs/features/keyboard-shortcuts.mdx b/docs/features/keyboard-shortcuts.mdx index 9ca020369..9c0584479 100644 --- a/docs/features/keyboard-shortcuts.mdx +++ b/docs/features/keyboard-shortcuts.mdx @@ -151,7 +151,7 @@ Inherits the standard `NSDocument` shortcuts. | Save | `Cmd+S` | | Save As | `Cmd+Shift+S` | | Revert to Saved | `Cmd+Option+S` | -| Toggle filter bar | `Cmd+F` | +| Toggle filter panel | `Cmd+F` | | Copy selected rows as TSV | `Cmd+C` | | Paste TSV as new rows | `Cmd+V` | | Add secondary sort column | `Shift+Click` column header |