-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Hand keyboard focus to the opened panel after a right-sidebar file drop #11059
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b0a785a
98d1b20
a61d9db
6273402
cd9ae00
6f2e36f
d7f9362
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -103,6 +103,9 @@ final class MarkdownPanel: Panel, ObservableObject, FilePreviewTextEditingPanel | |||||||
| private var saveGeneration: Int = 0 | ||||||||
| private var activeSaveGeneration: Int? | ||||||||
| private var pendingSearchNeedle: String? | ||||||||
| /// Set when activation asks a preview panel to focus before SwiftUI has | ||||||||
| /// mounted its WKWebView. The renderer fulfills this at window attach. | ||||||||
| private var pendingPreviewFocus = false | ||||||||
| private weak var textView: NSTextView? | ||||||||
| private var isClosed: Bool = false | ||||||||
| // NotificationCenter token; removal is thread-safe so deinit can drop it. | ||||||||
|
|
@@ -138,6 +141,7 @@ final class MarkdownPanel: Panel, ObservableObject, FilePreviewTextEditingPanel | |||||||
| startWatching() | ||||||||
| observeTypographyDefaults() | ||||||||
| rendererSession.onMarkdownRendered = { [weak self] in | ||||||||
| self?.replayPendingPreviewFocusAfterWindowAttach() | ||||||||
| self?.replayActiveFindAfterRender() | ||||||||
| } | ||||||||
| } | ||||||||
|
|
@@ -371,17 +375,43 @@ final class MarkdownPanel: Panel, ObservableObject, FilePreviewTextEditingPanel | |||||||
| // MARK: - Panel protocol | ||||||||
|
|
||||||||
| func focus() { | ||||||||
| guard displayMode == .text else { return } | ||||||||
| _ = textView?.window?.makeFirstResponder(textView) | ||||||||
| applyPendingSearchNeedleIfPossible() | ||||||||
| if displayMode == .text { | ||||||||
| pendingPreviewFocus = false | ||||||||
| _ = textView?.window?.makeFirstResponder(textView) | ||||||||
| applyPendingSearchNeedleIfPossible() | ||||||||
| return | ||||||||
| } | ||||||||
| // Preview mode: the rendered web view is the panel's keyboard | ||||||||
| // surface. Taking first responder on activation is what moves the | ||||||||
| // keyboard out of wherever it was (for example the right-sidebar | ||||||||
| // file list after a click- or drag-open), so the find/shortcut | ||||||||
| // router targets this panel — the same behavior terminal and | ||||||||
| // browser panels have. No-op while the web view is not mounted; | ||||||||
| // the drop/open paths also hand off focus at the coordinator level. | ||||||||
| guard let webView = rendererSession.webView, let window = webView.window else { | ||||||||
| pendingPreviewFocus = true | ||||||||
| return | ||||||||
| } | ||||||||
| let didBecomeFirstResponder = window.makeFirstResponder(webView) | ||||||||
| && window.firstResponder === webView | ||||||||
|
Comment on lines
+395
to
+396
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When WebKit installs a descendant as the window’s first responder, this strict identity check treats a successful preview focus as pending. Later content renders then replay Prompt for AI agents
Suggested change
|
||||||||
| pendingPreviewFocus = !didBecomeFirstResponder | ||||||||
| } | ||||||||
|
|
||||||||
| /// Completes a preview focus request recorded before the renderer view was | ||||||||
| /// attached to its window. The callback is event-driven, so it cannot | ||||||||
| /// steal focus after this panel has been unfocused in the meantime. | ||||||||
| func replayPendingPreviewFocusAfterWindowAttach() { | ||||||||
| guard pendingPreviewFocus, displayMode == .preview else { return } | ||||||||
| focus() | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: In preview mode, a pending focus request ( Prompt for AI agents |
||||||||
| } | ||||||||
|
|
||||||||
| func unfocus() { | ||||||||
| // No-op for read-only panel. | ||||||||
| pendingPreviewFocus = false | ||||||||
| } | ||||||||
|
|
||||||||
| func close() { | ||||||||
| isClosed = true | ||||||||
| pendingPreviewFocus = false | ||||||||
| searchState = nil | ||||||||
| rendererSession.close() | ||||||||
| GlobalSearchCoordinator.shared.purgePanel(id: id) | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12308,12 +12308,16 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos | |||||
| targetIndex: index | ||||||
| ).isEmpty | ||||||
| case .split(let paneId, let orientation, let insertFirst): | ||||||
| return splitPaneWithFileSurface( | ||||||
| guard let panel = splitPaneWithFileSurface( | ||||||
| targetPane: paneId, | ||||||
| orientation: orientation, | ||||||
| insertFirst: insertFirst, | ||||||
| filePath: entry.filePath | ||||||
| ) != nil | ||||||
| ) else { | ||||||
| return false | ||||||
| } | ||||||
| handKeyboardFocusFromRightSidebarAfterFileOpen(to: panel) | ||||||
| return true | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -12330,6 +12334,8 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos | |||||
|
|
||||||
| switch request.destination { | ||||||
| case .insert(let paneId, let index): | ||||||
| // openFileSurfaces(focus: true) performs the sidebar focus | ||||||
| // handoff itself. | ||||||
| return !openFileSurfaces( | ||||||
| inPane: paneId, | ||||||
| filePaths: entries.map(\.filePath), | ||||||
|
|
@@ -12354,10 +12360,27 @@ final class Workspace: Identifiable, ObservableObject, FilePreviewTabMetadataHos | |||||
| filePaths: entries.dropFirst().map(\.filePath), | ||||||
| focus: true | ||||||
| ) | ||||||
| handKeyboardFocusFromRightSidebarAfterFileOpen(to: firstPanel) | ||||||
| return true | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// A sidebar-initiated open (click on a file row, or a drag whose | ||||||
| /// mouse-down made the sidebar first responder) never resigns the | ||||||
| /// sidebar's keyboard focus by itself, and a freshly created panel's | ||||||
| /// view may not be mounted yet when activation asks it to take first | ||||||
| /// responder. Without this handoff the find/shortcut router keeps | ||||||
| /// targeting the sidebar (Cmd+F lands in its file search instead of the | ||||||
| /// just-opened document). Hand keyboard focus to the opened panel the | ||||||
| /// same way the text-drop path does; the call is a no-op when the | ||||||
| /// sidebar does not own focus (opens from Finder, the CLI, or between | ||||||
| /// panes). | ||||||
| func handKeyboardFocusFromRightSidebarAfterFileOpen(to panel: any Panel) { | ||||||
| _ = AppDelegate.shared?.restoreMainPanelKeyboardFocusFromRightSidebar( | ||||||
| in: activationWindow(for: panel) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: When another main window is key during a file drop, this fallback can select the wrong focus coordinator because Markdown and file-preview panels do not expose their owning window here. Resolve the window from this workspace's owning tab manager before restoring focus, or pass the drop's source/target window explicitly. Prompt for AI agents
Suggested change
|
||||||
| ) | ||||||
| } | ||||||
|
|
||||||
| @discardableResult | ||||||
| private func splitPaneWithFileSurface( | ||||||
| targetPane paneId: PaneID, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: When a focused open targets a non-key main window, this handoff can resolve the wrong window and leave the target sidebar as first responder, so Cmd+F still routes to the sidebar. Resolve and pass the target workspace's owning window instead of falling back to
NSApp.keyWindow/mainWindow.Prompt for AI agents