mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
macos: simplify command palette sort keys (#13872)
Store the Comparable ObjectIdentifier directly instead of wrapping the only sort key type in AnySortKey. The expected deterministic ordering of equal terminal command titles is also now verified by a unit test.
This commit is contained in:
@@ -20,7 +20,7 @@ struct CommandOption: Identifiable, Hashable {
|
||||
/// Whether to visually emphasize this option.
|
||||
let emphasis: Bool
|
||||
/// Sort key for stable ordering when titles are equal.
|
||||
let sortKey: AnySortKey?
|
||||
let sortKey: ObjectIdentifier?
|
||||
/// The action to perform when this option is selected.
|
||||
let action: () -> Void
|
||||
|
||||
@@ -33,7 +33,7 @@ struct CommandOption: Identifiable, Hashable {
|
||||
leadingColor: Color? = nil,
|
||||
badge: String? = nil,
|
||||
emphasis: Bool = false,
|
||||
sortKey: AnySortKey? = nil,
|
||||
sortKey: ObjectIdentifier? = nil,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
func sortedTerminalPaletteOptions(_ options: [CommandOption]) -> [CommandOption] {
|
||||
options.sorted { lhs, rhs in
|
||||
let lhsTitle = lhs.title.replacingOccurrences(of: ":", with: "\t")
|
||||
let rhsTitle = rhs.title.replacingOccurrences(of: ":", with: "\t")
|
||||
let comparison = lhsTitle.localizedCaseInsensitiveCompare(rhsTitle)
|
||||
if comparison != .orderedSame {
|
||||
return comparison == .orderedAscending
|
||||
}
|
||||
if let lhsKey = lhs.sortKey, let rhsKey = rhs.sortKey {
|
||||
return lhsKey < rhsKey
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
struct TerminalCommandPaletteView: View {
|
||||
/// The surface that this command palette represents.
|
||||
let surfaceView: Ghostty.SurfaceView
|
||||
@@ -64,19 +79,7 @@ struct TerminalCommandPaletteView: View {
|
||||
// Sort the rest. We replace ":" with a character that sorts before space
|
||||
// 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")
|
||||
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
|
||||
})
|
||||
options.append(contentsOf: sortedTerminalPaletteOptions(jumpOptions + terminalOptions))
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -168,7 +171,7 @@ struct TerminalCommandPaletteView: View {
|
||||
subtitle: subtitle,
|
||||
leadingIcon: "rectangle.on.rectangle",
|
||||
leadingColor: displayColor?.displayColor.map { Color($0) },
|
||||
sortKey: AnySortKey(ObjectIdentifier(surface))
|
||||
sortKey: ObjectIdentifier(surface)
|
||||
) {
|
||||
NotificationCenter.default.post(
|
||||
name: Ghostty.Notification.ghosttyPresentTerminal,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,15 @@ struct CommandPaletteFilterTests {
|
||||
title: String,
|
||||
subtitle: String? = nil,
|
||||
description: String? = nil,
|
||||
leadingColor: Color? = nil
|
||||
leadingColor: Color? = nil,
|
||||
sortKey: ObjectIdentifier? = nil
|
||||
) -> CommandOption {
|
||||
CommandOption(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
description: description,
|
||||
leadingColor: leadingColor
|
||||
leadingColor: leadingColor,
|
||||
sortKey: sortKey
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -46,4 +48,24 @@ struct CommandPaletteFilterTests {
|
||||
#expect([first, second].filteredAndSorted(query: "new") == [first, second])
|
||||
#expect([second, first].filteredAndSorted(query: "new") == [second, first])
|
||||
}
|
||||
|
||||
/// Equal titles use their sort keys independent of input order.
|
||||
@Test func equalTitlesUseSortKey() {
|
||||
let firstKey = NSObject()
|
||||
let secondKey = NSObject()
|
||||
let first = option(
|
||||
title: "Focus: Shell",
|
||||
subtitle: "/tmp",
|
||||
sortKey: ObjectIdentifier(firstKey)
|
||||
)
|
||||
let second = option(
|
||||
title: "Focus: Shell",
|
||||
subtitle: "/tmp",
|
||||
sortKey: ObjectIdentifier(secondKey)
|
||||
)
|
||||
|
||||
let forward = sortedTerminalPaletteOptions([first, second])
|
||||
let reverse = sortedTerminalPaletteOptions([second, first])
|
||||
#expect(forward == reverse)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user