Skip to content
Merged
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: 4 additions & 7 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
34590 CLI/cmux.swift
17841 Sources/AppDelegate.swift
16499 Sources/ContentView.swift
16132 Sources/ContentView.swift
13832 Sources/TerminalController.swift
12828 Sources/Workspace.swift
12237 Sources/GhosttyTerminalView.swift
Expand All @@ -17,7 +17,7 @@
6901 Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
6317 cmuxTests/SessionPersistenceTests.swift
6217 cmuxTests/GhosttyConfigTests.swift
6187 Sources/TabManager.swift
6183 Sources/TabManager.swift
6084 Sources/TextBoxInput.swift
5915 cmuxTests/TerminalAndGhosttyTests.swift
5573 cmuxTests/BrowserConfigTests.swift
Expand Down Expand Up @@ -85,7 +85,6 @@
1093 Sources/RemoteTmuxController.swift
1093 cmuxUITests/BonsplitTabDragUITests.swift
1087 Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Search/CommandPaletteFuzzyMatcher.swift
1030 cmuxTests/WorkspaceGroupTests.swift
1021 cmuxUITests/TerminalCmdClickUITests.swift
1009 cmuxTests/CmuxTopSnapshotScopeTests.swift
1006 cmuxTests/CmuxSSHURLRequestTests.swift
Expand All @@ -98,9 +97,9 @@
928 Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/AppSection.swift
926 Sources/DockPanelView.swift
920 Sources/CommandPalette/CommandPaletteSettingsToggle.swift
918 cmuxTests/WorkspaceGroupTests.swift
905 Sources/CmuxSSHURLRequest.swift
899 Sources/Panels/MarkdownWebRenderer.swift
885 cmuxTests/SidebarWorkspaceDropPlannerTests.swift
885 Sources/Panels/TerminalPanel.swift
881 Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
877 Packages/Shared/CmuxAgentChat/Tests/CmuxAgentChatTests/ChatConversationStoreTests.swift
Expand Down Expand Up @@ -133,7 +132,6 @@
716 Sources/TaskManagerSnapshot.swift
715 Sources/AppleScriptSupport.swift
710 Sources/TerminalSSHSessionDetector.swift
709 Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderDropResolver.swift
706 CLI/CMUXCLI+Config.swift
705 Sources/Panels/BrowserPopupWindowController.swift
699 cmuxTests/TerminalNotificationClearAllTests.swift
Expand Down Expand Up @@ -169,7 +167,6 @@
608 Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceGroupCoordinator.swift
606 Sources/SettingsNavigation.swift
604 Packages/macOS/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteNucleoFFITests.swift
602 Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift
601 Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerPrimaryPolicies.swift
599 Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizer.swift
596 cmuxTests/CmuxEventBusTests.swift
Expand All @@ -185,7 +182,6 @@
580 cmuxTests/CLIHookNoResponseTests.swift
578 Sources/RightSidebarPanelView.swift
577 cmuxTests/AppearanceSettingsTests.swift
574 Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/WorkspaceCoordinatorTests.swift
572 Sources/Feed/FeedTextEditorDebugWindowController.swift
568 Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalRenderGrid.swift
567 Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
Expand All @@ -198,6 +194,7 @@
549 Sources/Panels/BrowserAutomation.swift
547 Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore.swift
541 Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Pane/ControlCommandCoordinator+Pane.swift
540 Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift
539 CLI/CMUXCLI+Themes.swift
539 CLI/CodexTeamsApprovalBridge.swift
538 Packages/macOS/CmuxRemoteWorkspace/Sources/CmuxRemoteWorkspace/PTYBridge/RemotePTYBridgeSession.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
public import Foundation

/// Which edge of a sidebar row a drop indicator is drawn against.
public enum SidebarDropEdge: Equatable, Sendable {
public enum SidebarDropEdge: Equatable {
case top
case bottom
}

/// Where the sidebar should render the drop indicator during a tab/workspace
/// drag: against the `top` or `bottom` edge of the row identified by `tabId`,
/// or at the end of the list when `tabId` is `nil`.
public struct SidebarDropIndicator: Equatable, Sendable {
public struct SidebarDropIndicator: Equatable {
public let tabId: UUID?
public let edge: SidebarDropEdge

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,12 @@ public struct SidebarDropPlanner {
pinnedTabIds: Set<UUID>,
legalInsertionRange: ClosedRange<Int>? = nil,
pointerY: CGFloat? = nil,
targetHeight: CGFloat? = nil,
preserveTargetEdge: Bool = false
) -> SidebarDropIndicator? {
indicator(
draggedTabId: draggedTabId,
targetTabId: targetTabId,
tabIds: tabIds,
pinnedTabIds: pinnedTabIds,
legalInsertionRange: legalInsertionRange,
pointerY: pointerY,
targetHeight: targetHeight,
preserveTargetEdge: preserveTargetEdge,
suppressesNoOp: true
)
}

func indicator(
draggedTabId: UUID?,
targetTabId: UUID?,
tabIds: [UUID],
pinnedTabIds: Set<UUID>,
legalInsertionRange: ClosedRange<Int>? = nil,
pointerY: CGFloat? = nil,
targetHeight: CGFloat? = nil,
preserveTargetEdge: Bool = false,
suppressesNoOp: Bool
targetHeight: CGFloat? = nil
) -> SidebarDropIndicator? {
guard tabIds.count > 1, let draggedTabId else { return nil }
guard let fromIndex = tabIds.firstIndex(of: draggedTabId) else { return nil }

let insertionPosition: Int
let proposedIndicator: SidebarDropIndicator?
if let targetTabId {
guard let targetTabIndex = tabIds.firstIndex(of: targetTabId) else { return nil }
let edge: SidebarDropEdge
Expand All @@ -56,10 +30,8 @@ public struct SidebarDropPlanner {
edge = preferredEdge(fromIndex: fromIndex, targetTabId: targetTabId, tabIds: tabIds)
}
insertionPosition = (edge == .bottom) ? targetTabIndex + 1 : targetTabIndex
proposedIndicator = SidebarDropIndicator(tabId: targetTabId, edge: edge)
} else {
insertionPosition = tabIds.count
proposedIndicator = SidebarDropIndicator(tabId: nil, edge: .bottom)
}

let legalInsertionPosition = legalInsertionPosition(
Expand All @@ -69,17 +41,12 @@ public struct SidebarDropPlanner {
pinnedTabIds: pinnedTabIds,
legalInsertionRange: legalInsertionRange
)
if suppressesNoOp {
let legalTargetIndex = resolvedTargetIndex(
from: fromIndex,
insertionPosition: legalInsertionPosition,
totalCount: tabIds.count
)
guard legalTargetIndex != fromIndex else { return nil }
}
if preserveTargetEdge, legalInsertionPosition == insertionPosition {
return proposedIndicator
}
let legalTargetIndex = resolvedTargetIndex(
from: fromIndex,
insertionPosition: legalInsertionPosition,
totalCount: tabIds.count
)
guard legalTargetIndex != fromIndex else { return nil }
return indicatorForInsertionPosition(legalInsertionPosition, tabIds: tabIds)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,100 +1,43 @@
public import Foundation

/// Pure predicates deciding when a sidebar row (or the empty area below all
/// rows) should render its drop indicator for a given drag state.
/// rows) should render its "top" drop indicator for a given drag state.
public struct SidebarTabDropIndicatorPredicate {
/// Creates a sidebar drop-indicator predicate evaluator.
public init() {}

/// Returns whether the canonical gap indicator should render above a row.
///
/// A resolved `.bottom` edge on the previous visible row represents the
/// same visual divider as a `.top` edge on this row. The sidebar renders
/// that divider only here, above the row after the gap, so adjacent rows
/// cannot both draw competing drop lines.
///
/// - Parameters:
/// - tabId: The row currently being rendered.
/// - draggedTabId: The workspace currently being dragged, if any.
/// - dropIndicator: The resolved sidebar drop indicator.
/// - tabIds: Visible workspace row identifiers in display order.
/// - Returns: `true` when the resolved indicator targets the divider above this row.
public func topVisible(
forTabId tabId: UUID,
draggedTabId: UUID?,
dropIndicator: SidebarDropIndicator?,
tabIds: [UUID]
) -> Bool {
guard draggedTabId != nil,
let indicator = dropIndicator,
let rowIndex = tabIds.firstIndex(of: tabId) else { return false }

switch indicator.edge {
case .top:
return indicator.tabId == tabId
case .bottom:
guard let indicatorTabId = indicator.tabId,
let indicatorIndex = tabIds.firstIndex(of: indicatorTabId) else {
return false
}
return indicatorIndex + 1 == rowIndex
guard draggedTabId != nil, let indicator = dropIndicator else { return false }
if indicator.tabId == tabId && indicator.edge == .top {
return true
}
}

/// Returns whether the bottom-edge indicator should render below a row.
///
/// Row-to-row dividers are canonicalized to `topVisible` for the row after
/// the gap. Root-level final append dividers are rendered by
/// `emptyAreaTopVisible`; scoped group append dividers can be rendered below
/// the last visible row in that group.
///
/// - Parameters:
/// - tabId: The row currently being rendered.
/// - draggedTabId: The workspace currently being dragged, if any.
/// - dropIndicator: The resolved sidebar drop indicator.
/// - tabIds: Visible workspace row identifiers in display order.
/// - indicatorScope: The visible row scope where the resolved indicator is rendered.
/// - Returns: `true` when a scoped final append divider should render below this row.
public func bottomVisible(
forTabId tabId: UUID,
draggedTabId: UUID?,
dropIndicator: SidebarDropIndicator?,
tabIds: [UUID],
indicatorScope: SidebarWorkspaceReorderDropIndicatorScope = .raw
) -> Bool {
guard draggedTabId != nil,
indicatorScope.isGroup,
let indicator = dropIndicator,
indicator.edge == .bottom,
indicator.tabId == tabId,
tabIds.last == tabId else {
guard indicator.edge == .bottom,
let currentIndex = tabIds.firstIndex(of: tabId),
currentIndex > 0
else {
return false
}
return true
return tabIds[currentIndex - 1] == indicator.tabId
Comment on lines +18 to +24

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Avoid the per-row full scan in the drop-indicator predicate.

Line 19 runs tabIds.firstIndex(of:) from a predicate called by each sidebar row/header render, so a bottom-edge drag indicator turns the render pass into O(n²) over workspace rows. Pass a precomputed predecessor/index lookup from the render context and make this predicate O(1). As per path instructions, “In SwiftUI/body and drag-drop hit-testing/rendering paths, avoid repeated full scans over scalable collections.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarTabDropIndicatorPredicate.swift`
around lines 18 - 24, The predicate containing the guard statement that checks
`tabIds.firstIndex(of: tabId)` performs a full linear scan over the tabIds
collection for every sidebar row render, causing O(n²) performance. Instead of
scanning within the predicate, modify the method signature to accept a
precomputed index lookup (such as a dictionary mapping tabId to its index
position) as a parameter from the render context. Replace the
`tabIds.firstIndex(of: tabId)` call with an O(1) lookup from this precomputed
dictionary, and update the call sites to pass this lookup when invoking the
predicate.

Source: Path instructions

}

/// Convenience used by `SidebarEmptyArea`: the empty area's "top" indicator
/// (drawn above the empty space below all rows) is visible when the drop
/// indicator targets nothing (end-of-list), or when a `.bottom` indicator
/// targets the last visible row in the current drag scope.
///
/// - Parameters:
/// - draggedTabId: The workspace currently being dragged, if any.
/// - dropIndicator: The resolved sidebar drop indicator.
/// - lastTabId: The last visible row in the current root/top-level scope.
/// - indicatorScope: The visible row scope where the resolved indicator is rendered.
/// - Returns: `true` when the root/top-level empty area should render its divider.
/// indicator targets nothing (end-of-list) or the bottom edge of the last
/// row.
public func emptyAreaTopVisible(
draggedTabId: UUID?,
dropIndicator: SidebarDropIndicator?,
lastTabId: UUID?,
indicatorScope: SidebarWorkspaceReorderDropIndicatorScope = .raw
lastTabId: UUID?
) -> Bool {
guard !indicatorScope.isGroup else { return false }
guard draggedTabId != nil, let indicator = dropIndicator else { return false }
if indicator.tabId == nil {
return true
}
return indicator.edge == .bottom && indicator.tabId == lastTabId
guard indicator.edge == .bottom, let lastTabId else { return false }
return indicator.tabId == lastTabId
}
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading