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
120 changes: 117 additions & 3 deletions Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3591,9 +3591,36 @@ class TabManager: ObservableObject {

/// Resize split - not directly supported by bonsplit, but we can adjust divider positions
func resizeSplit(tabId: UUID, surfaceId: UUID, direction: ResizeDirection, amount: UInt16) -> Bool {
// Bonsplit handles resize through its own divider dragging
// This is a no-op for now as bonsplit manages divider positions internally
return false
guard amount > 0,
let tab = tabs.first(where: { $0.id == tabId }),
let paneId = tab.paneId(forPanelId: surfaceId) else { return false }

let paneUUID = paneId.id
guard tab.bonsplitController.allPaneIds.contains(where: { $0.id == paneUUID }) else {
return false
}

var candidates: [ResizeSplitCandidate] = []
let trace = resizeSplitCollectCandidates(
node: tab.bonsplitController.treeSnapshot(),
targetPaneId: paneUUID.uuidString,
candidates: &candidates
)
guard trace.containsTarget else { return false }

let orientationMatches = candidates.filter { $0.orientation == direction.splitOrientation }
guard !orientationMatches.isEmpty else { return false }

guard let candidate = orientationMatches.first(where: {
$0.paneInFirstChild == direction.requiresPaneInFirstChild
}) else {
return false
}

let delta = CGFloat(amount) / candidate.axisPixels
let requested = candidate.dividerPosition + (direction.dividerDeltaSign * delta)
let clamped = min(max(requested, 0.1), 0.9)
return tab.bonsplitController.setDividerPosition(clamped, forSplit: candidate.splitId, fromExternal: true)
}

/// Equalize splits - not directly supported by bonsplit
Expand Down Expand Up @@ -3660,6 +3687,68 @@ class TabManager: ObservableObject {
}
}

private struct ResizeSplitCandidate {
let splitId: UUID
let orientation: String
let paneInFirstChild: Bool
let dividerPosition: CGFloat
let axisPixels: CGFloat
}

private struct ResizeSplitTrace {
let containsTarget: Bool
let bounds: CGRect
}

private func resizeSplitCollectCandidates(
node: ExternalTreeNode,
targetPaneId: String,
candidates: inout [ResizeSplitCandidate]
) -> ResizeSplitTrace {
switch node {
case .pane(let pane):
let bounds = CGRect(
x: pane.frame.x,
y: pane.frame.y,
width: pane.frame.width,
height: pane.frame.height
)
return ResizeSplitTrace(containsTarget: pane.id == targetPaneId, bounds: bounds)

case .split(let split):
let first = resizeSplitCollectCandidates(
node: split.first,
targetPaneId: targetPaneId,
candidates: &candidates
)
let second = resizeSplitCollectCandidates(
node: split.second,
targetPaneId: targetPaneId,
candidates: &candidates
)

let combinedBounds = first.bounds.union(second.bounds)
let containsTarget = first.containsTarget || second.containsTarget

if containsTarget,
let splitUUID = UUID(uuidString: split.id) {
let orientation = split.orientation.lowercased()
let axisPixels: CGFloat = orientation == "horizontal"
? combinedBounds.width
: combinedBounds.height
candidates.append(ResizeSplitCandidate(
splitId: splitUUID,
orientation: orientation,
paneInFirstChild: first.containsTarget,
dividerPosition: CGFloat(split.dividerPosition),
axisPixels: max(axisPixels, 1)
))
}

return ResizeSplitTrace(containsTarget: containsTarget, bounds: combinedBounds)
}
}
Comment on lines +3690 to +3750

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Duplicated resize logic already exists in TerminalController

ResizeSplitCandidate, ResizeSplitTrace, and resizeSplitCollectCandidates are near-identical copies of V2PaneResizeCandidate, V2PaneResizeTrace, and v2PaneResizeCollectCandidates (lines ~5853–5944 in TerminalController.swift). Similarly, the new splitOrientation, requiresPaneInFirstChild, and dividerDeltaSign properties on ResizeDirection are functionally identical to those on the private V2PaneResizeDirection enum.

Now that TabManager.resizeSplit is correctly implemented, TerminalController.v2PaneResize could be simplified to call it (mapping V2PaneResizeDirection to ResizeDirection), or both could share a common internal helper. Leaving these two paths to diverge independently will make future changes (e.g., changing clamp bounds, fixing a traversal bug) error-prone — they'd need to be applied in two places.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping the refactor out of this PR to keep the fix scope narrow. I addressed the missing coverage feedback in follow-up test commit 75c94247; if maintainers want the shared-helper consolidation, I’d treat that as a separate cleanup change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agree this is worth consolidating. Keeping it out of this PR to avoid scope creep. The merge-with-main push (8c5eec0) resolves the conflict; a follow-up cleanup PR can unify the two paths.


/// Close a surface/panel
func closeSurface(tabId: UUID, surfaceId: UUID) -> Bool {
guard let tab = tabs.first(where: { $0.id == tabId }) else { return false }
Expand Down Expand Up @@ -5362,6 +5451,31 @@ enum SplitDirection {
/// Resize direction for backwards compatibility
enum ResizeDirection {
case left, right, up, down

var splitOrientation: String {
switch self {
case .left, .right:
return "horizontal"
case .up, .down:
return "vertical"
}
}

/// A split controls the target pane's right/bottom edge when the target is
/// the first child, and left/top edge when the target is the second child.
var requiresPaneInFirstChild: Bool {
switch self {
case .right, .down:
return true
case .left, .up:
return false
}
}

/// Positive values move the divider toward the second child (right/down).
var dividerDeltaSign: CGFloat {
requiresPaneInFirstChild ? 1 : -1
}
}

extension Notification.Name {
Expand Down
Loading
Loading