From d3a3dab74a9df312d41eb2b8d64c8e148752e66e Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Mon, 30 Mar 2026 21:08:34 -0700 Subject: [PATCH] Live-update cmd-p workspace titles --- Sources/ContentView.swift | 193 ++++++++++++++++++++++++++++++-------- 1 file changed, 152 insertions(+), 41 deletions(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 1870b8ea..48cd80db 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1732,12 +1732,53 @@ struct ContentView: View { let shortcutHint: String? let kindLabel: String? let keywords: [String] + let liveTitleWorkspace: Workspace? let dismissOnRun: Bool let action: () -> Void + init( + id: String, + rank: Int, + title: String, + subtitle: String, + shortcutHint: String?, + kindLabel: String?, + keywords: [String], + liveTitleWorkspace: Workspace? = nil, + dismissOnRun: Bool, + action: @escaping () -> Void + ) { + self.id = id + self.rank = rank + self.title = title + self.subtitle = subtitle + self.shortcutHint = shortcutHint + self.kindLabel = kindLabel + self.keywords = keywords + self.liveTitleWorkspace = liveTitleWorkspace + self.dismissOnRun = dismissOnRun + self.action = action + } + var searchableTexts: [String] { [title, subtitle] + keywords } + + func displayTitle() -> String { + guard let liveTitleWorkspace else { return title } + return ContentView.commandPaletteWorkspaceDisplayName(liveTitleWorkspace) + } + + func displayTitleMatchIndices( + matchingQuery: String, + fallbackIndices: Set + ) -> Set { + guard liveTitleWorkspace != nil else { return fallbackIndices } + return CommandPaletteFuzzyMatcher.matchCharacterIndices( + query: matchingQuery, + candidate: displayTitle() + ) + } } private struct CommandPaletteUsageEntry: Codable, Sendable { @@ -1991,6 +2032,39 @@ struct ContentView: View { var id: String { command.id } } + private struct CommandPaletteLiveWorkspaceResultLabel: View { + @ObservedObject private var workspace: Workspace + private let command: CommandPaletteCommand + private let matchingQuery: String + private let fallbackMatchIndices: Set + private let trailingLabel: CommandPaletteTrailingLabel? + + init( + workspace: Workspace, + command: CommandPaletteCommand, + matchingQuery: String, + fallbackMatchIndices: Set, + trailingLabel: CommandPaletteTrailingLabel? + ) { + _workspace = ObservedObject(wrappedValue: workspace) + self.command = command + self.matchingQuery = matchingQuery + self.fallbackMatchIndices = fallbackMatchIndices + self.trailingLabel = trailingLabel + } + + var body: some View { + ContentView.commandPaletteResultLabelContent( + title: command.displayTitle(), + matchedIndices: command.displayTitleMatchIndices( + matchingQuery: matchingQuery, + fallbackIndices: fallbackMatchIndices + ), + trailingLabel: trailingLabel + ) + } + } + private struct CommandPaletteResolvedSearchMatch: Sendable { let commandID: String let score: Int @@ -3680,6 +3754,7 @@ struct ContentView: View { ForEach(Array(visibleResults.enumerated()), id: \.element.id) { index, result in let isSelected = index == selectedIndex let isHovered = commandPaletteHoveredResultIndex == index + let trailingLabel = commandPaletteTrailingLabel(for: result.command) let rowBackground: Color = isSelected ? cmuxAccentColor().opacity(0.12) : (isHovered ? Color.primary.opacity(0.08) : .clear) @@ -3687,37 +3762,28 @@ struct ContentView: View { Button { runCommandPaletteResult(commandID: result.id) } label: { - HStack(spacing: 8) { - commandPaletteHighlightedTitleText( - result.command.title, - matchedIndices: result.titleMatchIndices - ) - .font(.system(size: 13, weight: .regular)) - .lineLimit(1) - Spacer() - - if let trailingLabel = commandPaletteTrailingLabel(for: result.command) { - switch trailingLabel.style { - case .shortcut: - Text(trailingLabel.text) - .font(.system(size: 11, weight: .medium)) - .foregroundStyle(.secondary) - .padding(.horizontal, 4) - .padding(.vertical, 1) - .background(Color.primary.opacity(0.08), in: RoundedRectangle(cornerRadius: 4, style: .continuous)) - case .kind: - Text(trailingLabel.text) - .font(.system(size: 11, weight: .regular)) - .foregroundStyle(.secondary) - .lineLimit(1) - } + Group { + if let liveTitleWorkspace = result.command.liveTitleWorkspace { + CommandPaletteLiveWorkspaceResultLabel( + workspace: liveTitleWorkspace, + command: result.command, + matchingQuery: commandPaletteQueryForMatching, + fallbackMatchIndices: result.titleMatchIndices, + trailingLabel: trailingLabel + ) + } else { + Self.commandPaletteResultLabelContent( + title: result.command.title, + matchedIndices: result.titleMatchIndices, + trailingLabel: trailingLabel + ) } } - .padding(.horizontal, 9) - .padding(.vertical, 2) - .frame(maxWidth: .infinity, alignment: .leading) - .background(rowBackground) - .contentShape(Rectangle()) + .padding(.horizontal, 9) + .padding(.vertical, 2) + .frame(maxWidth: .infinity, alignment: .leading) + .background(rowBackground) + .contentShape(Rectangle()) } .buttonStyle(.plain) .accessibilityIdentifier("CommandPaletteResultRow.\(index)") @@ -4814,7 +4880,7 @@ struct ContentView: View { return Self.commandPaletteSwitcherFingerprint(windowContexts: fingerprintContexts) } - private func commandPaletteHighlightedTitleText(_ title: String, matchedIndices: Set) -> Text { + private static func commandPaletteHighlightedTitleText(_ title: String, matchedIndices: Set) -> Text { guard !matchedIndices.isEmpty else { return Text(title).foregroundColor(.primary) } @@ -4842,6 +4908,46 @@ struct ContentView: View { return result } + @ViewBuilder + private static func commandPaletteTrailingLabelView(_ trailingLabel: CommandPaletteTrailingLabel?) -> some View { + if let trailingLabel { + switch trailingLabel.style { + case .shortcut: + Text(trailingLabel.text) + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(.secondary) + .padding(.horizontal, 4) + .padding(.vertical, 1) + .background( + Color.primary.opacity(0.08), + in: RoundedRectangle(cornerRadius: 4, style: .continuous) + ) + case .kind: + Text(trailingLabel.text) + .font(.system(size: 11, weight: .regular)) + .foregroundStyle(.secondary) + .lineLimit(1) + } + } + } + + private static func commandPaletteResultLabelContent( + title: String, + matchedIndices: Set, + trailingLabel: CommandPaletteTrailingLabel? + ) -> some View { + HStack(spacing: 8) { + commandPaletteHighlightedTitleText( + title, + matchedIndices: matchedIndices + ) + .font(.system(size: 13, weight: .regular)) + .lineLimit(1) + Spacer() + commandPaletteTrailingLabelView(trailingLabel) + } + } + private func commandPaletteTrailingLabel(for command: CommandPaletteCommand) -> CommandPaletteTrailingLabel? { if let shortcutHint = command.shortcutHint { return CommandPaletteTrailingLabel(text: shortcutHint, style: .shortcut) @@ -4896,10 +5002,11 @@ struct ContentView: View { id: workspaceCommandId, rank: nextRank, title: workspaceName, - subtitle: commandPaletteSwitcherSubtitle(base: String(localized: "commandPalette.switcher.workspaceLabel", defaultValue: "Workspace"), windowLabel: context.windowLabel), + subtitle: Self.commandPaletteSwitcherSubtitle(base: String(localized: "commandPalette.switcher.workspaceLabel", defaultValue: "Workspace"), windowLabel: context.windowLabel), shortcutHint: nil, kindLabel: String(localized: "commandPalette.kind.workspace", defaultValue: "Workspace"), keywords: workspaceKeywords, + liveTitleWorkspace: workspace, dismissOnRun: true, action: { focusCommandPaletteSwitcherTarget( @@ -4941,7 +5048,7 @@ struct ContentView: View { id: surfaceCommandId, rank: nextRank, title: surfaceName, - subtitle: commandPaletteSwitcherSubtitle(base: workspaceName, windowLabel: context.windowLabel), + subtitle: Self.commandPaletteSwitcherSubtitle(base: workspaceName, windowLabel: context.windowLabel), shortcutHint: nil, kindLabel: surfaceKindLabel, keywords: surfaceKeywords, @@ -5013,7 +5120,7 @@ struct ContentView: View { return contexts } - private func commandPaletteSwitcherSubtitle(base: String, windowLabel: String?) -> String { + private static func commandPaletteSwitcherSubtitle(base: String, windowLabel: String?) -> String { guard let windowLabel else { return base } return "\(base) • \(windowLabel)" } @@ -6480,7 +6587,7 @@ struct ContentView: View { return (workspace, panelId, panel) } - private func workspaceDisplayName(_ workspace: Workspace) -> String { + private static func commandPaletteWorkspaceDisplayName(_ workspace: Workspace) -> String { let custom = workspace.customTitle?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !custom.isEmpty { return custom @@ -6489,6 +6596,10 @@ struct ContentView: View { return title.isEmpty ? String(localized: "workspace.displayName.fallback", defaultValue: "Workspace") : title } + private func workspaceDisplayName(_ workspace: Workspace) -> String { + Self.commandPaletteWorkspaceDisplayName(workspace) + } + private func panelDisplayName(workspace: Workspace, panelId: UUID, fallback: String) -> String { let title = workspace.panelTitle(panelId: panelId)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" if !title.isEmpty { @@ -6925,13 +7036,13 @@ struct ContentView: View { } let rows = Array(commandPaletteVisibleResults.prefix(20)).map { result in - CommandPaletteDebugResultRow( - commandId: result.command.id, - title: result.command.title, - shortcutHint: result.command.shortcutHint, - trailingLabel: commandPaletteTrailingLabel(for: result.command)?.text, - score: result.score - ) + CommandPaletteDebugResultRow( + commandId: result.command.id, + title: result.command.displayTitle(), + shortcutHint: result.command.shortcutHint, + trailingLabel: commandPaletteTrailingLabel(for: result.command)?.text, + score: result.score + ) } return CommandPaletteDebugSnapshot(