Live-update cmd-p workspace titles

This commit is contained in:
Lawrence Chen 2026-03-30 21:08:34 -07:00
parent c7bb691285
commit d3a3dab74a
No known key found for this signature in database

View file

@ -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<Int>
) -> Set<Int> {
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<Int>
private let trailingLabel: CommandPaletteTrailingLabel?
init(
workspace: Workspace,
command: CommandPaletteCommand,
matchingQuery: String,
fallbackMatchIndices: Set<Int>,
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<Int>) -> Text {
private static func commandPaletteHighlightedTitleText(_ title: String, matchedIndices: Set<Int>) -> 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<Int>,
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(