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

View File

@ -67,11 +67,20 @@ struct TerminalCommandPaletteView: View {
options.append(contentsOf: updateOptions)
// 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
let aNormalized = a.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
}
@ -153,7 +162,8 @@ struct TerminalCommandPaletteView: View {
title: "Focus: \(displayTitle)",
subtitle: subtitle,
leadingIcon: "rectangle.on.rectangle",
leadingColor: displayColor?.displayColor.map { Color($0) }
leadingColor: displayColor?.displayColor.map { Color($0) },
sortKey: AnySortKey(ObjectIdentifier(surface))
) {
NotificationCenter.default.post(
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
}
}