mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
macos: simplify command palette sort keys
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