feat: distinguish duplicate application names by folder

This commit is contained in:
2026-10-11 21:30:05 +02:00
parent 3714fc516b
commit 77ed65a22a
2 changed files with 51 additions and 7 deletions
+49 -7
View File
@@ -11,6 +11,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private let history: LaunchHistory
private var applications: [Application] = []
private var results: [Application] = []
private var locationLabels: [URL: String] = [:]
private var discoveryTask: Task<Void, Never>?
private var isLaunching = false
@@ -228,11 +229,34 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}.value
guard let self else { return }
self.applications = applications
self.updateLocationLabels()
self.discoveryTask = nil
self.updateResults(preservingSelection: true)
}
}
private func updateLocationLabels() {
var nameCounts: [String: Int] = [:]
nameCounts.reserveCapacity(applications.count)
for application in applications { nameCounts[application.name, default: 0] += 1 }
var groups: [String: [(url: URL, folder: URL)]] = [:]
for application in applications where (nameCounts[application.name] ?? 0) > 1 {
groups[application.name, default: []].append(
(application.url, application.url.deletingLastPathComponent())
)
}
locationLabels.removeAll(keepingCapacity: true)
for group in groups.values {
var folderCounts: [URL: Int] = [:]
folderCounts.reserveCapacity(group.count)
for entry in group { folderCounts[entry.folder, default: 0] += 1 }
for entry in group {
let location = (folderCounts[entry.folder] ?? 0) > 1 ? entry.url : entry.folder
locationLabels[entry.url] = (location.path as NSString).abbreviatingWithTildeInPath
}
}
}
private func updateResults(preservingSelection: Bool = false) {
let selectedURL = preservingSelection && results.indices.contains(tableView.selectedRow)
? results[tableView.selectedRow].id : nil
@@ -299,10 +323,13 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let identifier = NSUserInterfaceItemIdentifier("applicationCell")
let cell = (tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView)
let cell = (tableView.makeView(withIdentifier: identifier, owner: self) as? ApplicationCell)
?? makeCell(identifier: identifier)
let application = results[row]
cell.textField?.stringValue = application.name
let location = locationLabels[application.id]
cell.locationLabel.stringValue = location ?? ""
cell.locationLabel.isHidden = location == nil
cell.imageView?.image = NSWorkspace.shared.icon(forFile: application.url.path)
cell.toolTip = application.url.path
return cell
@@ -316,8 +343,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
return view
}
private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> NSTableCellView {
let cell = NSTableCellView()
private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> ApplicationCell {
let cell = ApplicationCell()
cell.identifier = identifier
let icon = NSImageView()
icon.imageScaling = .scaleProportionallyDown
@@ -326,6 +353,16 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = SunsetPalette.text
label.lineBreakMode = .byTruncatingTail
let location = cell.locationLabel
location.font = .systemFont(ofSize: 12)
location.textColor = SunsetPalette.secondaryText
location.lineBreakMode = .byTruncatingMiddle
location.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
location.isHidden = true
let title = NSStackView(views: [label, location])
title.orientation = .horizontal
title.alignment = .centerY
title.spacing = 8
let kind = NSTextField(labelWithString: "Application")
kind.font = .systemFont(ofSize: 14)
kind.textColor = SunsetPalette.secondaryText
@@ -333,7 +370,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
kind.setAccessibilityElement(false)
cell.imageView = icon
cell.textField = label
for view in [icon, label, kind] {
for view in [icon, title, kind] {
view.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(view)
}
@@ -342,9 +379,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
icon.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
icon.widthAnchor.constraint(equalToConstant: 22),
icon.heightAnchor.constraint(equalToConstant: 22),
label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 12),
label.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
label.trailingAnchor.constraint(lessThanOrEqualTo: kind.leadingAnchor, constant: -12),
title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 12),
title.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
title.trailingAnchor.constraint(lessThanOrEqualTo: kind.leadingAnchor, constant: -12),
kind.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
kind.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -8),
])
@@ -374,6 +411,11 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
}
}
@MainActor
private final class ApplicationCell: NSTableCellView {
let locationLabel = NSTextField(labelWithString: "")
}
@MainActor
private final class ApplicationTableView: NSTableView {
var openSelection: (() -> Void)?