diff --git a/Sources/BrowserWindowPortal.swift b/Sources/BrowserWindowPortal.swift index 695a2065e0ff..e2cda7192096 100644 --- a/Sources/BrowserWindowPortal.swift +++ b/Sources/BrowserWindowPortal.swift @@ -8,6 +8,7 @@ private var cmuxWindowBrowserPortalKey: UInt8 = 0 private var cmuxWindowBrowserPortalCloseObserverKey: UInt8 = 0 private var cmuxBrowserSearchOverlayPanelIdAssociationKey: UInt8 = 0 private var cmuxBrowserPortalNeedsRenderingStateReattachKey: UInt8 = 0 +private var cmuxWindowInteractiveSplitDividerDragKey: UInt8 = 0 #if DEBUG private func browserPortalDebugToken(_ view: NSView?) -> String { @@ -44,6 +45,35 @@ private extension NSResponder { } } +private extension NSWindow { + var browserPortalHasInteractiveSplitDividerDrag: Bool { + get { + let isActive = + (objc_getAssociatedObject(self, &cmuxWindowInteractiveSplitDividerDragKey) as? NSNumber)? + .boolValue ?? false + guard isActive else { return false } + guard (NSEvent.pressedMouseButtons & 1) != 0 else { + objc_setAssociatedObject( + self, + &cmuxWindowInteractiveSplitDividerDragKey, + NSNumber(value: false), + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + return false + } + return true + } + set { + objc_setAssociatedObject( + self, + &cmuxWindowInteractiveSplitDividerDragKey, + NSNumber(value: newValue), + .OBJC_ASSOCIATION_RETAIN_NONATOMIC + ) + } + } +} + private extension WKWebView { private var browserPortalNeedsRenderingStateReattach: Bool { get { @@ -2057,6 +2087,13 @@ final class WindowBrowserSlotView: NSView { final class WindowBrowserPortal: NSObject { private static let transientRecoveryRetryBudget: Int = 12 + private static func dividerHitRectContains(_ point: NSPoint, rect: NSRect) -> Bool { + point.x >= rect.minX && + point.x <= rect.maxX && + point.y >= rect.minY && + point.y <= rect.maxY + } + private weak var window: NSWindow? private let hostView = WindowBrowserHostView(frame: .zero) private weak var installedContainerView: NSView? @@ -2119,7 +2156,60 @@ final class WindowBrowserPortal: NSObject { return !isInteractiveSplitDividerDrag(in: window) } + private static func noteInteractiveSplitDividerDragIfNeeded( + _ splitView: NSSplitView, + window: NSWindow, + hostView: WindowBrowserHostView + ) { + guard splitView.window === window else { return } + guard !splitView.isDescendant(of: hostView) else { return } + guard (NSEvent.pressedMouseButtons & 1) != 0 else { return } + guard let event = NSApp.currentEvent else { return } + let now = ProcessInfo.processInfo.systemUptime + guard (now - event.timestamp) < 0.1 else { return } + guard event.window === window else { return } + switch event.type { + case .leftMouseDown, .leftMouseDragged: + break + default: + return + } + guard splitView.arrangedSubviews.count >= 2 else { return } + + let location = splitView.convert(event.locationInWindow, from: nil) + let first = splitView.arrangedSubviews[0].frame + let second = splitView.arrangedSubviews[1].frame + let thickness = splitView.dividerThickness + let dividerRect: NSRect + + if splitView.isVertical { + guard first.width > 1, second.width > 1 else { return } + dividerRect = NSRect( + x: max(0, first.maxX), + y: 0, + width: thickness, + height: splitView.bounds.height + ) + } else { + guard first.height > 1, second.height > 1 else { return } + dividerRect = NSRect( + x: 0, + y: max(0, first.maxY), + width: splitView.bounds.width, + height: thickness + ) + } + + let hitRect = dividerRect.insetBy(dx: -5, dy: -5) + if dividerHitRectContains(location, rect: hitRect) { + window.browserPortalHasInteractiveSplitDividerDrag = true + } + } + private static func isInteractiveSplitDividerDrag(in window: NSWindow) -> Bool { + if window.browserPortalHasInteractiveSplitDividerDrag { + return true + } guard (NSEvent.pressedMouseButtons & 1) != 0 else { return false } guard let event = NSApp.currentEvent else { return false } let now = ProcessInfo.processInfo.systemUptime @@ -2155,6 +2245,22 @@ final class WindowBrowserPortal: NSObject { self?.scheduleExternalGeometrySynchronize() } }) + geometryObservers.append(center.addObserver( + forName: NSSplitView.willResizeSubviewsNotification, + object: nil, + queue: .main + ) { [weak self] notification in + MainActor.assumeIsolated { + guard let self, + let splitView = notification.object as? NSSplitView, + let window = self.window else { return } + Self.noteInteractiveSplitDividerDragIfNeeded( + splitView, + window: window, + hostView: self.hostView + ) + } + }) geometryObservers.append(center.addObserver( forName: NSSplitView.didResizeSubviewsNotification, object: nil, diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index de0e48826f89..84aed4afc84f 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1503,6 +1503,20 @@ class TabManager: ObservableObject { } } + private func scheduleInitialWorkspaceGitMetadataRefresh( + workspaceId: UUID, + panelId: UUID, + directory: String + ) { + scheduleWorkspaceGitMetadataRefresh( + workspaceId: workspaceId, + panelId: panelId, + directory: directory, + delays: Self.initialWorkspaceGitProbeDelays, + reason: "initial" + ) + } + private func scheduleWorkspaceGitMetadataRefresh( workspaceId: UUID, panelId: UUID, diff --git a/cmuxTests/TabManagerUnitTests.swift b/cmuxTests/TabManagerUnitTests.swift index ad8d5014e902..e3948cbfbbc6 100644 --- a/cmuxTests/TabManagerUnitTests.swift +++ b/cmuxTests/TabManagerUnitTests.swift @@ -710,8 +710,8 @@ final class TabManagerPullRequestProbeTests: XCTestCase { return } - workspace.updatePanelDirectory(panelId: panelId, directory: repoURL.path) - workspace.updatePanelGitBranch(panelId: panelId, branch: "feature/sidebar-pr", isDirty: false) + manager.updateSurfaceDirectory(tabId: workspace.id, surfaceId: panelId, directory: repoURL.path) + manager.updateSurfaceGitBranch(tabId: workspace.id, surfaceId: panelId, branch: "feature/sidebar-pr", isDirty: false) workspace.updatePanelPullRequest( panelId: panelId, number: 1052, diff --git a/vendor/bonsplit b/vendor/bonsplit index aff9f7073125..b2788b1e77d4 160000 --- a/vendor/bonsplit +++ b/vendor/bonsplit @@ -1 +1 @@ -Subproject commit aff9f7073125d412fed07bce1adab0e78f907b14 +Subproject commit b2788b1e77d43f0c114dcf189aa59cae8abb47de