Skip to content

fix(editor): focus the SQL editor when opening a new tab (#1765)#1766

Open
datlechin wants to merge 2 commits into
mainfrom
fix/new-tab-editor-focus
Open

fix(editor): focus the SQL editor when opening a new tab (#1765)#1766
datlechin wants to merge 2 commits into
mainfrom
fix/new-tab-editor-focus

Conversation

@datlechin

Copy link
Copy Markdown
Member

Fixes #1765.

Problem

Opening a new SQL editor tab with ⌘T often put keyboard focus in the sidebar "Filter" search field instead of the editor, so typed SQL went into the filter. Reliable repro: connect, ⌘T (focus correct), ⌘T again, focus jumps to the filter field. It also happens with no tabs open when the filter field already has focus.

Root cause

A new tab's window never sets initialFirstResponder, so AppKit auto-assigns focus to the geometrically-leading focusable view (the sidebar filter field) when the window becomes key. SQLEditorCoordinator.prepareCoordinator() tried to claim focus 50ms later, but only when window.firstResponder == nil || === window, so it backed off the moment the filter field already held focus. That guard inferred user intent from responder state, which carries no meaning on a window AppKit just pre-filled.

Fix

Carry focus as an explicit one-shot intent instead of inferring it from responder state:

  • QueryTabManager.addTab(claimFocus:) sets pendingFocusTabId (@ObservationIgnored, so it never triggers a render loop).
  • Both new-tab paths opt in: in-place addTab and the new-window .newEmptyTab path. Restored tabs and table/content tabs do not, so they never steal focus.
  • The view layer reads the flag for the matching tab, threads claimFocusOnAppear through QueryEditorView to SQLEditorView, and clears the flag in .onAppear (read-only in body).
  • SQLEditorView calls coordinator.scheduleEditorFocusClaim(); prepareCoordinator then calls makeFirstResponder unconditionally when the latch is set, with an acceptsFirstResponder precheck. The old firstResponder == nil check stays only as the fallback for non-intent cases.

The native API (deferred makeFirstResponder, the only way to focus the CodeEditSourceEditor NSTextView, which is opaque to SwiftUI @FocusState) was already correct; only the gating changed.

Tests

  • QueryTabManagerFocusTests (new): claimFocus sets/updates pendingFocusTabId; absent it stays nil; file-reopen dedup and addTableTab do not set it.
  • SQLEditorCoordinatorTests (extended): the focus-claim latch starts false, scheduleEditorFocusClaim() flips it, idempotent.

The live makeFirstResponder path needs an NSWindow and a connection, so it is not headless-deterministic; it is verified by manual repro of both scenarios above. This matches the existing unit-only FilterFocusStateTests.

Notes

swiftlint lint --strict passes on the changed files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL editor should receive focus when creating a new tab

1 participant