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
17 changes: 17 additions & 0 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -13871,6 +13871,23 @@
}
}
},
"tabBar.autoTitle.pullRequest": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "PR #%lld"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "PR #%lld"
}
}
}
},
"tabBar.newTerminalTab": {
"extractionState": "manual",
"localizations": {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace+Bonsplit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ extension Workspace: @preconcurrency BonsplitDelegate {
pendingDetachedSurfaces[tabId] = DetachedSurfaceTransfer(
panelId: panelId,
panel: panel,
title: resolvedPanelTitle(panelId: panelId, fallback: transferFallbackTitle),
title: panelCustomTitles[panelId] ?? transferFallbackTitle,
icon: panel.displayIcon,
iconImageData: browserPanel?.faviconPNGData,
kind: surfaceKind(for: panel),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Workspace+Persistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ extension Workspace {
private func sessionPanelSnapshot(panelId: UUID, includeScrollback: Bool) -> SessionPanelSnapshot? {
guard let panel = panels[panelId] else { return nil }

let panelTitle = panelTitle(panelId: panelId)
let panelTitle = panelTitles[panelId] ?? panel.displayTitle
let customTitle = panelCustomTitles[panelId]
let directory = panelDirectories[panelId]
let isPinned = pinnedPanelIds.contains(panelId)
Expand Down
30 changes: 20 additions & 10 deletions Sources/Workspace+SidebarTelemetry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,15 @@ extension Workspace {
let state = SidebarGitBranchState(branch: normalizedBranch, isDirty: isDirty)
let existing = panelGitBranches[panelId]
let branchChanged = existing?.branch != nil && existing?.branch != normalizedBranch
var titleMetadataChanged = false
if existing?.branch != normalizedBranch || existing?.isDirty != isDirty {
panelGitBranches[panelId] = state
titleMetadataChanged = true
}
if branchChanged {
if panelPullRequests[panelId] != nil {
panelPullRequests.removeValue(forKey: panelId)
titleMetadataChanged = true
}
if panelId == focusedPanelId, pullRequest != nil {
pullRequest = nil
Expand All @@ -124,14 +127,20 @@ extension Workspace {
if panelId == focusedPanelId, gitBranch != state {
gitBranch = state
}
if titleMetadataChanged {
syncResolvedPanelTitle(panelId: panelId)
}
}

func clearPanelGitBranch(panelId: UUID) {
var titleMetadataChanged = false
if panelGitBranches[panelId] != nil {
panelGitBranches.removeValue(forKey: panelId)
titleMetadataChanged = true
}
if panelPullRequests[panelId] != nil {
panelPullRequests.removeValue(forKey: panelId)
titleMetadataChanged = true
}
if panelId == focusedPanelId {
if gitBranch != nil {
Expand All @@ -141,6 +150,9 @@ extension Workspace {
pullRequest = nil
}
}
if titleMetadataChanged {
syncResolvedPanelTitle(panelId: panelId)
}
}

func updatePanelPullRequest(
Expand Down Expand Up @@ -208,6 +220,7 @@ extension Workspace {
)
if existing != state {
panelPullRequests[panelId] = state
syncResolvedPanelTitle(panelId: panelId)
}
if panelId == focusedPanelId, pullRequest != state {
pullRequest = state
Expand All @@ -217,6 +230,7 @@ extension Workspace {
func clearPanelPullRequest(panelId: UUID) {
if panelPullRequests[panelId] != nil {
panelPullRequests.removeValue(forKey: panelId)
syncResolvedPanelTitle(panelId: panelId)
}
if panelId == focusedPanelId, pullRequest != nil {
pullRequest = nil
Expand Down Expand Up @@ -287,9 +301,13 @@ extension Workspace {
logEntries.removeAll()
progress = nil
gitBranch = nil
let panelIdsWithAutomaticTitles = Set(panelGitBranches.keys).union(panelPullRequests.keys)
panelGitBranches.removeAll()
pullRequest = nil
panelPullRequests.removeAll()
for panelId in panelIdsWithAutomaticTitles {
syncResolvedPanelTitle(panelId: panelId)
}
// Clears bypass updatePanelAgentState/clearPanelAgentState's per-surface notify, so fan
// it out here too -- otherwise a surface.wait `agent_state` (or a subscribed client)
// watching a surface whose state got wiped by a sidebar reset would hang until timeout
Expand Down Expand Up @@ -360,16 +378,8 @@ extension Workspace {
panelsWithLiveTitle.insert(panelId)

// Update bonsplit tab title only when this panel's title changed.
if didMutate,
let tabId = surfaceIdFromPanelId(panelId),
let panel = panels[panelId] {
let baseTitle = panelTitles[panelId] ?? panel.displayTitle
let resolvedTitle = resolvedPanelTitle(panelId: panelId, fallback: baseTitle)
bonsplitController.updateTab(
tabId,
title: resolvedTitle,
hasCustomTitle: panelCustomTitles[panelId] != nil
)
if didMutate {
syncResolvedPanelTitle(panelId: panelId)
}

// The focused pane titles the workspace (tmux-style). Single-panel
Expand Down
48 changes: 41 additions & 7 deletions Sources/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -992,9 +992,49 @@ final class Workspace: Identifiable, ObservableObject {
!custom.isEmpty {
return custom
}
if let pullRequest = panelPullRequests[panelId] {
let panelBranch = normalizedSidebarBranchName(panelGitBranches[panelId]?.branch)
let pullRequestBranch = normalizedSidebarBranchName(pullRequest.branch)
if pullRequestBranch == nil || panelBranch == pullRequestBranch {
return String(
localized: "tabBar.autoTitle.pullRequest",
defaultValue: "PR #\(pullRequest.number)"
)
}
}
if let branch = normalizedSidebarBranchName(panelGitBranches[panelId]?.branch) {
return Self.ticketIdentifier(in: branch) ?? branch
}
return fallbackTitle
}

private static let ticketIdentifierPattern = try? NSRegularExpression(
pattern: #"(?<![A-Za-z0-9])([A-Z][A-Z0-9]+-[0-9]+)(?![A-Za-z0-9])"#
)

private static func ticketIdentifier(in branch: String) -> String? {
guard let ticketIdentifierPattern,
let match = ticketIdentifierPattern.firstMatch(
in: branch,
range: NSRange(branch.startIndex..., in: branch)
),
match.numberOfRanges > 1,
let range = Range(match.range(at: 1), in: branch) else {
return nil
}
return String(branch[range])
}

func syncResolvedPanelTitle(panelId: UUID) {
guard let panel = panels[panelId], let tabId = surfaceIdFromPanelId(panelId) else { return }
let baseTitle = panelTitles[panelId] ?? panel.displayTitle
bonsplitController.updateTab(
tabId,
title: resolvedPanelTitle(panelId: panelId, fallback: baseTitle),
hasCustomTitle: panelCustomTitles[panelId] != nil
)
}

func syncPinnedStateForTab(_ tabId: TabID, panelId: UUID) {
let isPinned = pinnedPanelIds.contains(panelId)
if let panel = panels[panelId] {
Expand Down Expand Up @@ -1097,13 +1137,7 @@ final class Workspace: Identifiable, ObservableObject {
panelCustomTitles[panelId] = trimmed
}

guard let panel = panels[panelId], let tabId = surfaceIdFromPanelId(panelId) else { return }
let baseTitle = panelTitles[panelId] ?? panel.displayTitle
bonsplitController.updateTab(
tabId,
title: resolvedPanelTitle(panelId: panelId, fallback: baseTitle),
hasCustomTitle: panelCustomTitles[panelId] != nil
)
syncResolvedPanelTitle(panelId: panelId)
}

func isPanelPinned(_ panelId: UUID) -> Bool {
Expand Down
Loading
Loading