macos: stable sort for surfaces

pull/9945/head
Mitchell Hashimoto 2025-12-17 09:52:05 -08:00
parent 1fd3f27e26
commit d23f7e051f
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
3 changed files with 52 additions and 3 deletions

View File

@ -1,15 +1,27 @@
import SwiftUI import SwiftUI
struct CommandOption: Identifiable, Hashable { struct CommandOption: Identifiable, Hashable {
/// Unique identifier for this option.
let id = UUID() let id = UUID()
/// The primary text displayed for this command.
let title: String let title: String
/// Secondary text displayed below the title.
let subtitle: String? let subtitle: String?
/// Tooltip text shown on hover.
let description: String? let description: String?
/// Keyboard shortcut symbols to display.
let symbols: [String]? let symbols: [String]?
/// SF Symbol name for the leading icon.
let leadingIcon: String? let leadingIcon: String?
/// Color for the leading indicator circle.
let leadingColor: Color? let leadingColor: Color?
/// Badge text displayed as a pill.
let badge: String? let badge: String?
/// Whether to visually emphasize this option.
let emphasis: Bool let emphasis: Bool
/// Sort key for stable ordering when titles are equal.
let sortKey: AnySortKey?
/// The action to perform when this option is selected.
let action: () -> Void let action: () -> Void
init( init(
@ -21,6 +33,7 @@ struct CommandOption: Identifiable, Hashable {
leadingColor: Color? = nil, leadingColor: Color? = nil,
badge: String? = nil, badge: String? = nil,
emphasis: Bool = false, emphasis: Bool = false,
sortKey: AnySortKey? = nil,
action: @escaping () -> Void action: @escaping () -> Void
) { ) {
self.title = title self.title = title
@ -31,6 +44,7 @@ struct CommandOption: Identifiable, Hashable {
self.leadingColor = leadingColor self.leadingColor = leadingColor
self.badge = badge self.badge = badge
self.emphasis = emphasis self.emphasis = emphasis
self.sortKey = sortKey
self.action = action self.action = action
} }

View File

@ -67,11 +67,20 @@ struct TerminalCommandPaletteView: View {
options.append(contentsOf: updateOptions) options.append(contentsOf: updateOptions)
// Sort the rest. We replace ":" with a character that sorts before space // Sort the rest. We replace ":" with a character that sorts before space
// so that "Foo:" sorts before "Foo Bar:". // so that "Foo:" sorts before "Foo Bar:". Use sortKey as a tie-breaker
// for stable ordering when titles are equal.
options.append(contentsOf: (jumpOptions + terminalOptions).sorted { a, b in options.append(contentsOf: (jumpOptions + terminalOptions).sorted { a, b in
let aNormalized = a.title.replacingOccurrences(of: ":", with: "\t") let aNormalized = a.title.replacingOccurrences(of: ":", with: "\t")
let bNormalized = b.title.replacingOccurrences(of: ":", with: "\t") let bNormalized = b.title.replacingOccurrences(of: ":", with: "\t")
return aNormalized.localizedCaseInsensitiveCompare(bNormalized) == .orderedAscending let comparison = aNormalized.localizedCaseInsensitiveCompare(bNormalized)
if comparison != .orderedSame {
return comparison == .orderedAscending
}
// Tie-breaker: use sortKey if both have one
if let aSortKey = a.sortKey, let bSortKey = b.sortKey {
return aSortKey < bSortKey
}
return false
}) })
return options return options
} }
@ -153,7 +162,8 @@ struct TerminalCommandPaletteView: View {
title: "Focus: \(displayTitle)", title: "Focus: \(displayTitle)",
subtitle: subtitle, subtitle: subtitle,
leadingIcon: "rectangle.on.rectangle", leadingIcon: "rectangle.on.rectangle",
leadingColor: displayColor?.displayColor.map { Color($0) } leadingColor: displayColor?.displayColor.map { Color($0) },
sortKey: AnySortKey(ObjectIdentifier(surface))
) { ) {
NotificationCenter.default.post( NotificationCenter.default.post(
name: Ghostty.Notification.ghosttyPresentTerminal, name: Ghostty.Notification.ghosttyPresentTerminal,

View File

@ -0,0 +1,25 @@
import Foundation
/// Type-erased wrapper for any Comparable type to use as a sort key.
struct AnySortKey: Comparable {
private let value: Any
private let comparator: (Any, Any) -> ComparisonResult
init<T: Comparable>(_ value: T) {
self.value = value
self.comparator = { lhs, rhs in
guard let l = lhs as? T, let r = rhs as? T else { return .orderedSame }
if l < r { return .orderedAscending }
if l > r { return .orderedDescending }
return .orderedSame
}
}
static func < (lhs: AnySortKey, rhs: AnySortKey) -> Bool {
lhs.comparator(lhs.value, rhs.value) == .orderedAscending
}
static func == (lhs: AnySortKey, rhs: AnySortKey) -> Bool {
lhs.comparator(lhs.value, rhs.value) == .orderedSame
}
}