Fix equalize splits to recursively set all dividers to 0.5 (#575)
The equalize splits command was a no-op that always returned false. Implement it by recursively walking the bonsplit tree and setting every split divider position to 0.5. Also register the command in the command palette with a "workspace has splits" precondition so it only appears when there are multiple panes. Adds a regression test that creates a nested split layout, skews divider positions, equalizes, and verifies all dividers are at 0.5. Fixes https://github.com/manaflow-ai/cmux/issues/571
This commit is contained in:
parent
b1846aaec4
commit
780f959a48
3 changed files with 114 additions and 3 deletions
|
|
@ -1385,6 +1385,7 @@ struct ContentView: View {
|
|||
static let workspaceHasCustomName = "workspace.hasCustomName"
|
||||
static let workspaceShouldPin = "workspace.shouldPin"
|
||||
static let workspaceHasPullRequests = "workspace.hasPullRequests"
|
||||
static let workspaceHasSplits = "workspace.hasSplits"
|
||||
|
||||
static let hasFocusedPanel = "panel.hasFocus"
|
||||
static let panelName = "panel.name"
|
||||
|
|
@ -3373,6 +3374,10 @@ struct ContentView: View {
|
|||
CommandPaletteContextKeys.workspaceHasPullRequests,
|
||||
!workspace.sidebarPullRequestsInDisplayOrder().isEmpty
|
||||
)
|
||||
snapshot.setBool(
|
||||
CommandPaletteContextKeys.workspaceHasSplits,
|
||||
workspace.bonsplitController.allPaneIds.count > 1
|
||||
)
|
||||
}
|
||||
|
||||
if let panelContext = focusedPanelContext {
|
||||
|
|
@ -3938,6 +3943,15 @@ struct ContentView: View {
|
|||
when: { $0.bool(CommandPaletteContextKeys.panelIsTerminal) }
|
||||
)
|
||||
)
|
||||
contributions.append(
|
||||
CommandPaletteCommandContribution(
|
||||
commandId: "palette.equalizeSplits",
|
||||
title: constant("Equalize Splits"),
|
||||
subtitle: workspaceSubtitle,
|
||||
keywords: ["split", "equalize", "balance", "divider", "layout"],
|
||||
when: { $0.bool(CommandPaletteContextKeys.workspaceHasSplits) }
|
||||
)
|
||||
)
|
||||
|
||||
return contributions
|
||||
}
|
||||
|
|
@ -4180,6 +4194,13 @@ struct ContentView: View {
|
|||
registry.register(commandId: "palette.terminalSplitBrowserDown") {
|
||||
_ = tabManager.createBrowserSplit(direction: .down)
|
||||
}
|
||||
registry.register(commandId: "palette.equalizeSplits") {
|
||||
guard let workspace = tabManager.selectedWorkspace,
|
||||
tabManager.equalizeSplits(tabId: workspace.id) else {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var focusedPanelContext: (workspace: Workspace, panelId: UUID, panel: any Panel)? {
|
||||
|
|
|
|||
|
|
@ -2006,9 +2006,17 @@ class TabManager: ObservableObject {
|
|||
|
||||
/// Equalize splits - not directly supported by bonsplit
|
||||
func equalizeSplits(tabId: UUID) -> Bool {
|
||||
// Bonsplit doesn't have a built-in equalize feature
|
||||
// This would require manually setting all divider positions to 0.5
|
||||
return false
|
||||
guard let tab = tabs.first(where: { $0.id == tabId }) else { return false }
|
||||
|
||||
var foundSplit = false
|
||||
var allSucceeded = true
|
||||
equalizeSplits(
|
||||
in: tab.bonsplitController.treeSnapshot(),
|
||||
controller: tab.bonsplitController,
|
||||
foundSplit: &foundSplit,
|
||||
allSucceeded: &allSucceeded
|
||||
)
|
||||
return foundSplit && allSucceeded
|
||||
}
|
||||
|
||||
/// Toggle zoom on a panel - bonsplit doesn't have zoom support
|
||||
|
|
@ -2017,6 +2025,41 @@ class TabManager: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
private func equalizeSplits(
|
||||
in node: ExternalTreeNode,
|
||||
controller: BonsplitController,
|
||||
foundSplit: inout Bool,
|
||||
allSucceeded: inout Bool
|
||||
) {
|
||||
switch node {
|
||||
case .pane:
|
||||
return
|
||||
case .split(let splitNode):
|
||||
foundSplit = true
|
||||
guard let splitId = UUID(uuidString: splitNode.id) else {
|
||||
allSucceeded = false
|
||||
return
|
||||
}
|
||||
|
||||
if !controller.setDividerPosition(0.5, forSplit: splitId) {
|
||||
allSucceeded = false
|
||||
}
|
||||
|
||||
equalizeSplits(
|
||||
in: splitNode.first,
|
||||
controller: controller,
|
||||
foundSplit: &foundSplit,
|
||||
allSucceeded: &allSucceeded
|
||||
)
|
||||
equalizeSplits(
|
||||
in: splitNode.second,
|
||||
controller: controller,
|
||||
foundSplit: &foundSplit,
|
||||
allSucceeded: &allSucceeded
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Close a surface/panel
|
||||
func closeSurface(tabId: UUID, surfaceId: UUID) -> Bool {
|
||||
guard let tab = tabs.first(where: { $0.id == tabId }) else { return false }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import SwiftUI
|
|||
import WebKit
|
||||
import SwiftUI
|
||||
import ObjectiveC.runtime
|
||||
import Bonsplit
|
||||
|
||||
#if canImport(cmux_DEV)
|
||||
@testable import cmux_DEV
|
||||
|
|
@ -3317,6 +3318,52 @@ final class TabManagerSurfaceCreationTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TabManagerEqualizeSplitsTests: XCTestCase {
|
||||
func testEqualizeSplitsSetsEverySplitDividerToHalf() {
|
||||
let manager = TabManager()
|
||||
guard let workspace = manager.selectedWorkspace,
|
||||
let leftPanelId = workspace.focusedPanelId,
|
||||
let rightPanel = workspace.newTerminalSplit(from: leftPanelId, orientation: .horizontal),
|
||||
workspace.newTerminalSplit(from: rightPanel.id, orientation: .vertical) != nil else {
|
||||
XCTFail("Expected nested split setup to succeed")
|
||||
return
|
||||
}
|
||||
|
||||
let initialSplits = splitNodes(in: workspace.bonsplitController.treeSnapshot())
|
||||
XCTAssertGreaterThanOrEqual(initialSplits.count, 2, "Expected at least two split nodes in nested layout")
|
||||
|
||||
for (index, split) in initialSplits.enumerated() {
|
||||
guard let splitId = UUID(uuidString: split.id) else {
|
||||
XCTFail("Expected split ID to be a UUID")
|
||||
return
|
||||
}
|
||||
let targetPosition: CGFloat = index.isMultiple(of: 2) ? 0.2 : 0.8
|
||||
XCTAssertTrue(
|
||||
workspace.bonsplitController.setDividerPosition(targetPosition, forSplit: splitId),
|
||||
"Expected to seed divider position for split \(splitId)"
|
||||
)
|
||||
}
|
||||
|
||||
XCTAssertTrue(manager.equalizeSplits(tabId: workspace.id), "Expected equalize splits command to succeed")
|
||||
|
||||
let equalizedSplits = splitNodes(in: workspace.bonsplitController.treeSnapshot())
|
||||
XCTAssertEqual(equalizedSplits.count, initialSplits.count)
|
||||
for split in equalizedSplits {
|
||||
XCTAssertEqual(split.dividerPosition, 0.5, accuracy: 0.000_1)
|
||||
}
|
||||
}
|
||||
|
||||
private func splitNodes(in node: ExternalTreeNode) -> [ExternalSplitNode] {
|
||||
switch node {
|
||||
case .pane:
|
||||
return []
|
||||
case .split(let split):
|
||||
return [split] + splitNodes(in: split.first) + splitNodes(in: split.second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class WorkspaceTerminalConfigInheritanceSelectionTests: XCTestCase {
|
||||
func testPrefersSelectedTerminalInTargetPaneOverFocusedTerminalElsewhere() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue