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
3 changes: 3 additions & 0 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11618,6 +11618,9 @@ struct VerticalTabsSidebar: View, Equatable {
}
let rowActions = SidebarAppKitRowActions(
commands: commands,
onOpenStatusURL: { url in
NSWorkspace.shared.open(url)
},
onOpenPullRequest: { [prefer = input.settings.openPullRequestLinksInCmuxBrowser] url in
openInBrowser(url, prefer)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,14 @@ final class SidebarWorkspaceRowTableCellView: NSTableCellView {
} else {
entryColor = explicitColor ?? .secondaryLabelColor
}
metadataRows[index].configureMetadataEntry(entry, model: model, color: entryColor)
metadataRows[index].configureMetadataEntry(
entry,
model: model,
color: entryColor
) { [weak self] url in
self?.actions?.commands.updateSelection()
self?.actions?.onOpenStatusURL(url)
}
}
let toggleFont = NSFont.systemFont(ofSize: model.scaled(10), weight: .semibold)
let toggleColor = model.isActive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct SidebarWorkspaceRowModel: Equatable {
@MainActor
struct SidebarAppKitRowActions {
let commands: SidebarWorkspaceRowCommands
let onOpenStatusURL: (URL) -> Void
let onOpenPullRequest: (URL) -> Void
let onOpenPort: (Int) -> Void
let onToggleChecklistExpansion: () -> Void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ final class SidebarRowIconTextLine: NSView {
private let iconView = NSImageView()
private let iconLabel = NSTextField(labelWithString: "")
private let textView = SidebarRowTextView(lines: 1)
private let metadataButton = SidebarRowLinkButton()
private let secondTextView = SidebarRowTextView(lines: 1)
private var iconSize: CGFloat = 0
private var stacked = false
Expand All @@ -64,6 +65,9 @@ final class SidebarRowIconTextLine: NSView {
addSubview(iconView)
addSubview(iconLabel)
addSubview(textView)
metadataButton.alignment = .left
metadataButton.isHidden = true
addSubview(metadataButton)
secondTextView.isHidden = true
addSubview(secondTextView)
}
Expand All @@ -75,7 +79,8 @@ final class SidebarRowIconTextLine: NSView {
func configureMetadataEntry(
_ entry: SidebarStatusEntry,
model: SidebarWorkspaceRowModel,
color: NSColor
color: NSColor,
onOpenURL: @escaping (URL) -> Void
) {
stacked = false
secondTextView.isHidden = true
Expand Down Expand Up @@ -106,9 +111,25 @@ final class SidebarRowIconTextLine: NSView {
}
}
}
textView.stringValue = entry.sidebarDisplayText
textView.font = .systemFont(ofSize: model.scaled(10))
textView.textColor = color
let font = NSFont.systemFont(ofSize: model.scaled(10))
if let url = entry.url {
textView.isHidden = true
metadataButton.isHidden = false
metadataButton.configure(
title: entry.sidebarDisplayText,
font: font,
color: color,
underlined: true,
toolTip: url.absoluteString,
onClick: { onOpenURL(url) }
)
} else {
metadataButton.isHidden = true
textView.isHidden = false
textView.stringValue = entry.sidebarDisplayText
textView.font = font
textView.textColor = color
}
needsLayout = true
}

Expand All @@ -118,6 +139,8 @@ final class SidebarRowIconTextLine: NSView {
palette: SidebarRowPalette
) {
stacked = false
metadataButton.isHidden = true
textView.isHidden = false
secondTextView.isHidden = true
iconLabel.isHidden = true
let iconName: String
Expand Down Expand Up @@ -163,6 +186,8 @@ final class SidebarRowIconTextLine: NSView {
model: SidebarWorkspaceRowModel,
palette: SidebarRowPalette
) {
metadataButton.isHidden = true
textView.isHidden = false
iconView.isHidden = true
iconLabel.isHidden = true
iconSize = 0
Expand Down Expand Up @@ -207,7 +232,9 @@ final class SidebarRowIconTextLine: NSView {

func measuredHeight(width: CGFloat) -> CGFloat {
resolveCandidates(width: width)
let first = textView.measuredHeight(width: max(10, width - iconSize))
let first = metadataButton.isHidden
? textView.measuredHeight(width: max(10, width - iconSize))
: ceil(metadataButton.intrinsicContentSize.height)
let second = secondTextView.isHidden ? 0 : secondTextView.measuredHeight(width: max(10, width - iconSize)) + 1
return first + second
}
Expand Down Expand Up @@ -240,8 +267,12 @@ final class SidebarRowIconTextLine: NSView {
icon.frame = NSRect(x: 0, y: 1, width: side, height: side)
x = side + 4
}
let firstHeight = textView.measuredHeight(width: max(10, bounds.width - x))
textView.frame = NSRect(x: x, y: 0, width: max(10, bounds.width - x), height: firstHeight)
let availableWidth = max(10, bounds.width - x)
let firstHeight = metadataButton.isHidden
? textView.measuredHeight(width: availableWidth)
: ceil(metadataButton.intrinsicContentSize.height)
let primaryView: NSView = metadataButton.isHidden ? textView : metadataButton
primaryView.frame = NSRect(x: x, y: 0, width: availableWidth, height: firstHeight)
if !secondTextView.isHidden {
let secondHeight = secondTextView.measuredHeight(width: max(10, bounds.width - x))
secondTextView.frame = NSRect(x: x, y: firstHeight + 1, width: max(10, bounds.width - x), height: secondHeight)
Expand Down
48 changes: 40 additions & 8 deletions cmuxTests/SidebarAppKitRowCellTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import Testing
@Suite
@MainActor
struct SidebarAppKitRowCellTests {
private static func makeSnapshot(title: String = "Workspace") -> SidebarWorkspaceSnapshotBuilder.Snapshot {
private static func makeSnapshot(
title: String = "Workspace",
metadataEntries: [SidebarStatusEntry] = []
) -> SidebarWorkspaceSnapshotBuilder.Snapshot {
SidebarWorkspaceSnapshotBuilder.Snapshot(
presentationKey: SidebarWorkspaceSnapshotFactory.presentationKey(
settings: SidebarTabItemSettingsSnapshot(defaults: UserDefaults(suiteName: UUID().uuidString)!),
Expand All @@ -24,7 +27,7 @@ struct SidebarAppKitRowCellTests {
showsRemoteReconnectAffordance: false,
copyableSidebarSSHError: nil,
latestConversationMessage: nil,
metadataEntries: [],
metadataEntries: metadataEntries,
metadataBlocks: [],
latestLog: nil,
progress: nil,
Expand Down Expand Up @@ -52,14 +55,15 @@ struct SidebarAppKitRowCellTests {
workspaceId: UUID = UUID(),
isActive: Bool = false,
canClose: Bool = true,
settings: SidebarTabItemSettingsSnapshot? = nil
settings: SidebarTabItemSettingsSnapshot? = nil,
metadataEntries: [SidebarStatusEntry] = []
) -> SidebarWorkspaceRowModel {
let resolvedSettings = settings
?? SidebarTabItemSettingsSnapshot(defaults: UserDefaults(suiteName: UUID().uuidString)!)
return SidebarWorkspaceRowModel(
workspaceId: workspaceId,
index: 0,
snapshot: makeSnapshot(),
snapshot: makeSnapshot(metadataEntries: metadataEntries),
settings: resolvedSettings,
isActive: isActive,
isMultiSelected: false,
Expand Down Expand Up @@ -141,7 +145,10 @@ struct SidebarAppKitRowCellTests {
UserDefaults(suiteName: "SidebarAppKitRowCellTests.\(UUID().uuidString)")!
}

private static func makeActions(model: SidebarWorkspaceRowModel) -> SidebarAppKitRowActions {
private static func makeActions(
model: SidebarWorkspaceRowModel,
onOpenStatusURL: @escaping (URL) -> Void = { _ in }
) -> SidebarAppKitRowActions {
let commands = SidebarWorkspaceRowCommands(
tab: Workspace(),
tabManager: nil,
Expand All @@ -163,6 +170,7 @@ struct SidebarAppKitRowCellTests {
)
return SidebarAppKitRowActions(
commands: commands,
onOpenStatusURL: onOpenStatusURL,
onOpenPullRequest: { _ in },
onOpenPort: { _ in },
onToggleChecklistExpansion: {},
Expand All @@ -178,19 +186,24 @@ struct SidebarAppKitRowCellTests {
}

private static func configuredCell(
model: SidebarWorkspaceRowModel
model: SidebarWorkspaceRowModel,
onOpenStatusURL: @escaping (URL) -> Void = { _ in }
) -> SidebarWorkspaceRowTableCellView {
let cell = SidebarWorkspaceRowTableCellView()
cell.configure(
model: model,
actions: makeActions(model: model),
actions: makeActions(model: model, onOpenStatusURL: onOpenStatusURL),
isPointerHovering: false,
contextMenuDidOpen: {},
contextMenuDidClose: {}
)
return cell
}

private static func descendants(of view: NSView) -> [NSView] {
view.subviews + view.subviews.flatMap { descendants(of: $0) }
}

@Test(arguments: zip(["codex", "claude_code"], ["Running", "Needs input"]))
func metadataStatusTextOmitsRawAgentKey(_ key: String, _ status: String) throws {
let model = Self.makeModel()
Expand All @@ -199,14 +212,33 @@ struct SidebarAppKitRowCellTests {
row.configureMetadataEntry(
SidebarStatusEntry(key: key, value: status, icon: "bolt.fill"),
model: model,
color: .labelColor
color: .labelColor,
onOpenURL: { _ in }
)

let textView = try #require(row.subviews.compactMap { $0 as? SidebarRowTextView }.first)
#expect(textView.stringValue == status)
#expect(!textView.stringValue.contains(key))
}

@Test
func metadataStatusURLRendersAnActionBoundToItsDestination() throws {
let url = try #require(URL(string: "https://example.com/issues/8520"))
let model = Self.makeModel(
metadataEntries: [SidebarStatusEntry(key: "repro_link", value: "click me", url: url)]
)
var openedURL: URL?
let cell = Self.configuredCell(model: model) { openedURL = $0 }
let buttons = Self.descendants(of: cell).compactMap { $0 as? NSButton }

let link = try #require(buttons.first { $0.toolTip == url.absoluteString })
#expect(link.action != nil)
#expect(link.target != nil)
#expect(link.isEnabled)
link.performClick(nil)
#expect(openedURL == url)
}

@Test
func hoverEnforcementShortCircuitsWhenAlreadyCorrect() {
let model = Self.makeModel()
Expand Down
Loading