Fix stale sidebar git branch after split close
This commit is contained in:
parent
0e3e17ca75
commit
60e7aeeb16
5 changed files with 119 additions and 10 deletions
|
|
@ -107,9 +107,9 @@ _cmux_prompt_command() {
|
|||
local first
|
||||
first=$(git status --porcelain -uno 2>/dev/null | head -1)
|
||||
[[ -n "$first" ]] && dirty_opt="--status=dirty"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
else
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID"
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
fi
|
||||
} >/dev/null 2>&1 &
|
||||
_CMUX_GIT_JOB_PID=$!
|
||||
|
|
|
|||
|
|
@ -240,9 +240,9 @@ _cmux_precmd() {
|
|||
local first
|
||||
first=$(git status --porcelain -uno 2>/dev/null | head -1)
|
||||
[[ -n "$first" ]] && dirty_opt="--status=dirty"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID"
|
||||
_cmux_send "report_git_branch $branch $dirty_opt --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
else
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID"
|
||||
_cmux_send "clear_git_branch --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
|
||||
fi
|
||||
} >/dev/null 2>&1 &!
|
||||
_CMUX_GIT_JOB_PID=$!
|
||||
|
|
|
|||
|
|
@ -7552,8 +7552,8 @@ class TerminalController {
|
|||
list_log [--limit=N] [--tab=X] - List log entries
|
||||
set_progress <0.0-1.0> [--label=X] [--tab=X] - Set progress bar
|
||||
clear_progress [--tab=X] - Clear progress bar
|
||||
report_git_branch <branch> [--status=dirty] [--tab=X] - Report git branch
|
||||
clear_git_branch [--tab=X] - Clear git branch
|
||||
report_git_branch <branch> [--status=dirty] [--tab=X] [--panel=Y] - Report git branch
|
||||
clear_git_branch [--tab=X] [--panel=Y] - Clear git branch
|
||||
report_ports <port1> [port2...] [--tab=X] [--panel=Y] - Report listening ports
|
||||
report_tty <tty_name> [--tab=X] [--panel=Y] - Register TTY for batched port scanning
|
||||
ports_kick [--tab=X] [--panel=Y] - Request batched port scan for panel
|
||||
|
|
@ -10585,7 +10585,7 @@ class TerminalController {
|
|||
private func reportGitBranch(_ args: String) -> String {
|
||||
let parsed = parseOptions(args)
|
||||
guard let branch = parsed.positional.first else {
|
||||
return "ERROR: Missing branch name — usage: report_git_branch <branch> [--status=dirty] [--tab=X]"
|
||||
return "ERROR: Missing branch name — usage: report_git_branch <branch> [--status=dirty] [--tab=X] [--panel=Y]"
|
||||
}
|
||||
let isDirty = parsed.options["status"]?.lowercased() == "dirty"
|
||||
|
||||
|
|
@ -10595,19 +10595,78 @@ class TerminalController {
|
|||
result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected"
|
||||
return
|
||||
}
|
||||
tab.gitBranch = SidebarGitBranchState(branch: branch, isDirty: isDirty)
|
||||
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
|
||||
let panelArg = parsed.options["panel"] ?? parsed.options["surface"]
|
||||
let surfaceId: UUID
|
||||
if let panelArg {
|
||||
if panelArg.isEmpty {
|
||||
result = "ERROR: Missing panel id — usage: report_git_branch <branch> [--status=dirty] [--tab=X] [--panel=Y]"
|
||||
return
|
||||
}
|
||||
guard let parsedId = UUID(uuidString: panelArg) else {
|
||||
result = "ERROR: Invalid panel id '\(panelArg)'"
|
||||
return
|
||||
}
|
||||
surfaceId = parsedId
|
||||
} else {
|
||||
guard let focused = tab.focusedPanelId else {
|
||||
result = "ERROR: Missing panel id (no focused surface)"
|
||||
return
|
||||
}
|
||||
surfaceId = focused
|
||||
}
|
||||
|
||||
guard validSurfaceIds.contains(surfaceId) else {
|
||||
result = "ERROR: Panel not found '\(surfaceId.uuidString)'"
|
||||
return
|
||||
}
|
||||
|
||||
tab.updatePanelGitBranch(panelId: surfaceId, branch: branch, isDirty: isDirty)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func clearGitBranch(_ args: String) -> String {
|
||||
let parsed = parseOptions(args)
|
||||
var result = "OK"
|
||||
DispatchQueue.main.sync {
|
||||
guard let tab = resolveTabForReport(args) else {
|
||||
result = "ERROR: Tab not found"
|
||||
result = parsed.options["tab"] != nil ? "ERROR: Tab not found" : "ERROR: No tab selected"
|
||||
return
|
||||
}
|
||||
tab.gitBranch = nil
|
||||
|
||||
let validSurfaceIds = Set(tab.panels.keys)
|
||||
tab.pruneSurfaceMetadata(validSurfaceIds: validSurfaceIds)
|
||||
|
||||
let panelArg = parsed.options["panel"] ?? parsed.options["surface"]
|
||||
let surfaceId: UUID
|
||||
if let panelArg {
|
||||
if panelArg.isEmpty {
|
||||
result = "ERROR: Missing panel id — usage: clear_git_branch [--tab=X] [--panel=Y]"
|
||||
return
|
||||
}
|
||||
guard let parsedId = UUID(uuidString: panelArg) else {
|
||||
result = "ERROR: Invalid panel id '\(panelArg)'"
|
||||
return
|
||||
}
|
||||
surfaceId = parsedId
|
||||
} else {
|
||||
guard let focused = tab.focusedPanelId else {
|
||||
result = "ERROR: Missing panel id (no focused surface)"
|
||||
return
|
||||
}
|
||||
surfaceId = focused
|
||||
}
|
||||
|
||||
guard validSurfaceIds.contains(surfaceId) else {
|
||||
result = "ERROR: Panel not found '\(surfaceId.uuidString)'"
|
||||
return
|
||||
}
|
||||
|
||||
tab.clearPanelGitBranch(panelId: surfaceId)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -10898,6 +10957,7 @@ class TerminalController {
|
|||
tab.logEntries.removeAll()
|
||||
tab.progress = nil
|
||||
tab.gitBranch = nil
|
||||
tab.panelGitBranches.removeAll()
|
||||
tab.surfaceListeningPorts.removeAll()
|
||||
tab.listeningPorts.removeAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
@Published var logEntries: [SidebarLogEntry] = []
|
||||
@Published var progress: SidebarProgressState?
|
||||
@Published var gitBranch: SidebarGitBranchState?
|
||||
@Published var panelGitBranches: [UUID: SidebarGitBranchState] = [:]
|
||||
@Published var surfaceListeningPorts: [UUID: [Int]] = [:]
|
||||
@Published var listeningPorts: [Int] = []
|
||||
var surfaceTTYNames: [UUID: String] = [:]
|
||||
|
|
@ -513,6 +514,24 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func updatePanelGitBranch(panelId: UUID, branch: String, isDirty: Bool) {
|
||||
let state = SidebarGitBranchState(branch: branch, isDirty: isDirty)
|
||||
let existing = panelGitBranches[panelId]
|
||||
if existing?.branch != branch || existing?.isDirty != isDirty {
|
||||
panelGitBranches[panelId] = state
|
||||
}
|
||||
if panelId == focusedPanelId {
|
||||
gitBranch = state
|
||||
}
|
||||
}
|
||||
|
||||
func clearPanelGitBranch(panelId: UUID) {
|
||||
panelGitBranches.removeValue(forKey: panelId)
|
||||
if panelId == focusedPanelId {
|
||||
gitBranch = nil
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func updatePanelTitle(panelId: UUID, title: String) -> Bool {
|
||||
let trimmed = title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
|
@ -557,6 +576,7 @@ final class Workspace: Identifiable, ObservableObject {
|
|||
panelCustomTitles = panelCustomTitles.filter { validSurfaceIds.contains($0.key) }
|
||||
pinnedPanelIds = pinnedPanelIds.filter { validSurfaceIds.contains($0) }
|
||||
manualUnreadPanelIds = manualUnreadPanelIds.filter { validSurfaceIds.contains($0) }
|
||||
panelGitBranches = panelGitBranches.filter { validSurfaceIds.contains($0.key) }
|
||||
surfaceListeningPorts = surfaceListeningPorts.filter { validSurfaceIds.contains($0.key) }
|
||||
surfaceTTYNames = surfaceTTYNames.filter { validSurfaceIds.contains($0.key) }
|
||||
recomputeListeningPorts()
|
||||
|
|
@ -1539,6 +1559,7 @@ extension Workspace: BonsplitDelegate {
|
|||
if let dir = panelDirectories[panelId] {
|
||||
currentDirectory = dir
|
||||
}
|
||||
gitBranch = panelGitBranches[panelId]
|
||||
|
||||
// Post notification
|
||||
NotificationCenter.default.post(
|
||||
|
|
@ -1667,6 +1688,7 @@ extension Workspace: BonsplitDelegate {
|
|||
panels.removeValue(forKey: panelId)
|
||||
surfaceIdToPanelId.removeValue(forKey: tabId)
|
||||
panelDirectories.removeValue(forKey: panelId)
|
||||
panelGitBranches.removeValue(forKey: panelId)
|
||||
panelTitles.removeValue(forKey: panelId)
|
||||
panelCustomTitles.removeValue(forKey: panelId)
|
||||
pinnedPanelIds.remove(panelId)
|
||||
|
|
|
|||
|
|
@ -953,6 +953,33 @@ final class TabManagerSurfaceCreationTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class WorkspacePanelGitBranchTests: XCTestCase {
|
||||
func testClosingFocusedSplitRestoresBranchForRemainingFocusedPanel() {
|
||||
let workspace = Workspace()
|
||||
guard let firstPanelId = workspace.focusedPanelId else {
|
||||
XCTFail("Expected initial focused panel")
|
||||
return
|
||||
}
|
||||
|
||||
workspace.updatePanelGitBranch(panelId: firstPanelId, branch: "main", isDirty: false)
|
||||
guard let secondPanel = workspace.newTerminalSplit(from: firstPanelId, orientation: .horizontal) else {
|
||||
XCTFail("Expected split panel to be created")
|
||||
return
|
||||
}
|
||||
|
||||
workspace.updatePanelGitBranch(panelId: secondPanel.id, branch: "feature/bugfix", isDirty: true)
|
||||
XCTAssertEqual(workspace.focusedPanelId, secondPanel.id, "Expected split panel to be focused")
|
||||
XCTAssertEqual(workspace.gitBranch?.branch, "feature/bugfix")
|
||||
XCTAssertEqual(workspace.gitBranch?.isDirty, true)
|
||||
|
||||
XCTAssertTrue(workspace.closePanel(secondPanel.id, force: true), "Expected split panel close to succeed")
|
||||
XCTAssertEqual(workspace.focusedPanelId, firstPanelId, "Expected surviving panel to become focused")
|
||||
XCTAssertEqual(workspace.gitBranch?.branch, "main")
|
||||
XCTAssertEqual(workspace.gitBranch?.isDirty, false)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class BrowserPanelAddressBarFocusRequestTests: XCTestCase {
|
||||
func testRequestPersistsUntilAcknowledged() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue