feat: rank built-in commands alongside applications
This commit is contained in:
@@ -13,6 +13,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed)
|
||||
]
|
||||
private var visibleEntries: [Int] = []
|
||||
private var isCommand = false
|
||||
private var selectedEntry: Int? = 0
|
||||
private var rowTopConstraints: [NSLayoutConstraint] = []
|
||||
private var heightConstraint: NSLayoutConstraint?
|
||||
@@ -129,6 +130,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
let pill = anchor.superview ?? anchor
|
||||
_ = view
|
||||
applicationLabel.stringValue = application.name
|
||||
isCommand = application.command != nil
|
||||
let favorite = entries[1]
|
||||
let favoriteTitle = isFavorite ? "Remove Favorite" : "Favorite"
|
||||
favorite.button.attributedTitle = NSAttributedString(string: favoriteTitle, attributes: [
|
||||
@@ -201,7 +203,10 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
|
||||
|
||||
func controlTextDidChange(_ notification: Notification) {
|
||||
let query = search.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
visibleEntries = entries.indices.filter { query.isEmpty || entries[$0].button.title.range(of: query, options: [.caseInsensitive, .diacriticInsensitive]) != nil }
|
||||
visibleEntries = entries.indices.filter {
|
||||
(!isCommand || $0 == 0 || $0 == 1 || $0 == 3)
|
||||
&& (query.isEmpty || entries[$0].button.title.range(of: query, options: [.caseInsensitive, .diacriticInsensitive]) != nil)
|
||||
}
|
||||
selectedEntry = visibleEntries.first
|
||||
for index in entries.indices {
|
||||
let position = visibleEntries.firstIndex(of: index)
|
||||
|
||||
@@ -10,7 +10,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
private let sectionLabel = NSTextField(labelWithString: "Applications")
|
||||
private let openButton = NSButton(title: "Open Application", target: nil, action: nil)
|
||||
private let history: LaunchHistory
|
||||
private var applications: [Application] = []
|
||||
private var applications: [Application] = Application.commands
|
||||
private var results: [Application] = []
|
||||
private var calculation: CalculatorResult?
|
||||
private var isCurrencyCalculation = false
|
||||
@@ -32,10 +32,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
private var launcherViews: [NSView] = []
|
||||
private var launcherFrameBeforeReview: NSRect?
|
||||
private var translation: TranslationController?
|
||||
private var showsTranslationCommand = true
|
||||
private var translationCommandRow: Int? {
|
||||
calculation == nil && showsTranslationCommand ? results.count : nil
|
||||
}
|
||||
|
||||
init(history: LaunchHistory = LaunchHistory(), uninstaller: ApplicationUninstaller = ApplicationUninstaller()) {
|
||||
self.history = history
|
||||
@@ -74,8 +70,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
window.numberHintsChanged = { [weak self] in self?.updateNumberHints() }
|
||||
window.launchNumber = { [weak self] index in
|
||||
guard let self else { return }
|
||||
if index == self.translationCommandRow { self.showTranslation() }
|
||||
else if self.results.indices.contains(index) { self.openApplication(self.results[index]) }
|
||||
if self.results.indices.contains(index) { self.openApplication(self.results[index]) }
|
||||
}
|
||||
window.dragEnded = { [weak self] in
|
||||
guard let self else { return }
|
||||
@@ -300,8 +295,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
actionsButton.keyEquivalentModifierMask = .command
|
||||
actionsButton.target = self
|
||||
actionsButton.action = #selector(showActions)
|
||||
actionsButton.setAccessibilityLabel("Application actions")
|
||||
actionsButton.toolTip = "Actions for the selected application (Command-K)"
|
||||
actionsButton.setAccessibilityLabel("Entry actions")
|
||||
actionsButton.toolTip = "Actions for the selected application or command (Command-K)"
|
||||
actionsButton.isEnabled = false
|
||||
openButton.toolTip = "Open the selected application (Return). Use ↑ and ↓ to select."
|
||||
let returnKey = ShortcutIndicator("↵")
|
||||
@@ -375,7 +370,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
}.value
|
||||
guard let self else { return }
|
||||
guard !Task.isCancelled else { return }
|
||||
self.applications = applications
|
||||
self.applications = applications + Application.commands
|
||||
self.updateLocationLabels()
|
||||
self.discoveryTask = nil
|
||||
self.updateResults(preservingSelection: true)
|
||||
@@ -407,9 +402,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
private func updateLocationLabels() {
|
||||
var nameCounts: [String: Int] = [:]
|
||||
nameCounts.reserveCapacity(applications.count)
|
||||
for application in applications { nameCounts[application.name, default: 0] += 1 }
|
||||
for application in applications where application.command == nil { nameCounts[application.name, default: 0] += 1 }
|
||||
var groups: [String: [(url: URL, folder: URL)]] = [:]
|
||||
for application in applications where (nameCounts[application.name] ?? 0) > 1 {
|
||||
for application in applications where application.command == nil && (nameCounts[application.name] ?? 0) > 1 {
|
||||
groups[application.name, default: []].append(
|
||||
(application.url, application.url.deletingLastPathComponent())
|
||||
)
|
||||
@@ -458,9 +453,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
detail: "\(answer.detail) · \(cacheStatus)"
|
||||
)
|
||||
}
|
||||
showsTranslationCommand = query.split(whereSeparator: \.isWhitespace).allSatisfy {
|
||||
"Conjure Translate".localizedStandardContains(String($0))
|
||||
}
|
||||
if calculation != nil { (window as? LauncherPanel)?.resetNumberHints() }
|
||||
if calculation != nil {
|
||||
results = []
|
||||
@@ -472,7 +464,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
sectionLabel.stringValue = isCurrencyCalculation ? "Daily currency rates"
|
||||
: calculation == nil ? "Applications & Commands" : "Calculator"
|
||||
tableView.setAccessibilityLabel(isCurrencyCalculation ? "Daily currency conversion result"
|
||||
: calculation == nil ? "Applications" : "Calculator result")
|
||||
: calculation == nil ? "Applications and commands" : "Calculator result")
|
||||
tableView.reloadData()
|
||||
if resultCount > 0 {
|
||||
let row = selectedURL.flatMap { url in results.firstIndex { $0.id == url } } ?? 0
|
||||
@@ -486,8 +478,9 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
emptyLabel.stringValue = "Finding applications…"
|
||||
statusLabel.stringValue = "Finding applications…"
|
||||
} else {
|
||||
emptyLabel.stringValue = applications.isEmpty ? "No applications found" : "No matching applications"
|
||||
statusLabel.stringValue = "\(results.count) applications" + (showsTranslationCommand ? " · 1 command" : "")
|
||||
emptyLabel.stringValue = "No matching applications or commands"
|
||||
let commandCount = results.lazy.filter { $0.command != nil }.count
|
||||
statusLabel.stringValue = "\(results.count - commandCount) applications · \(commandCount) commands"
|
||||
}
|
||||
updateOpenButton()
|
||||
(window as? LauncherPanel)?.sampleNumberHints()
|
||||
@@ -519,7 +512,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
return true
|
||||
}
|
||||
|
||||
private var resultCount: Int { calculation == nil ? results.count + (showsTranslationCommand ? 1 : 0) : 1 }
|
||||
private var resultCount: Int { calculation == nil ? results.count : 1 }
|
||||
|
||||
private func moveSelection(by offset: Int) {
|
||||
guard translation == nil else { return }
|
||||
@@ -531,12 +524,14 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
|
||||
private func updateOpenButton() {
|
||||
let canAct = !isLaunching && launcherFrameBeforeReview == nil && translation == nil && tableView.selectedRow >= 0
|
||||
openButton.title = calculation == nil ? (tableView.selectedRow == translationCommandRow ? "Open Command" : "Open Application") : "Copy Answer"
|
||||
openButton.title = calculation == nil
|
||||
? (results.indices.contains(tableView.selectedRow) && results[tableView.selectedRow].command != nil
|
||||
? "Open Command" : "Open Application") : "Copy Answer"
|
||||
openButton.toolTip = calculation == nil
|
||||
? "Open the selected entry (Return). Use ↑ and ↓ to select."
|
||||
: "Copy the answer to the clipboard (Return)."
|
||||
openButton.isEnabled = canAct && (calculation.map { $0.copyValue != nil }
|
||||
?? (results.indices.contains(tableView.selectedRow) || tableView.selectedRow == translationCommandRow))
|
||||
?? results.indices.contains(tableView.selectedRow))
|
||||
actionsButton.isEnabled = canAct && calculation == nil && results.indices.contains(tableView.selectedRow)
|
||||
}
|
||||
|
||||
@@ -574,11 +569,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
.withSymbolConfiguration(.init(pointSize: LauncherRowView.iconSize, weight: .regular))
|
||||
cell.imageView?.contentTintColor = SunsetPalette.accent
|
||||
cell.toolTip = calculation.detail
|
||||
} else if row == translationCommandRow {
|
||||
cell.textField?.stringValue = "Conjure Translate"
|
||||
cell.textField?.setAccessibilityLabel(nil)
|
||||
} else if results[row].command != nil {
|
||||
let entry = results[row]
|
||||
cell.textField?.stringValue = entry.name
|
||||
cell.textField?.setAccessibilityLabel(history.isFavorite(entry) ? "\(entry.name), Favorite" : nil)
|
||||
cell.locationLabel.isHidden = true
|
||||
cell.kindLabel.stringValue = "Command"
|
||||
cell.kindLabel.stringValue = history.isFavorite(entry) ? "★ Command" : "Command"
|
||||
cell.imageView?.image = NSImage(systemSymbolName: "character.bubble", accessibilityDescription: nil)
|
||||
cell.imageView?.contentTintColor = SunsetPalette.accent
|
||||
cell.toolTip = "Translate English to Japanese"
|
||||
@@ -723,7 +719,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
|
||||
@objc private func openSelection() {
|
||||
guard !isLaunching, translation == nil, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return }
|
||||
if tableView.selectedRow == translationCommandRow { showTranslation(); return }
|
||||
if let calculation {
|
||||
guard tableView.selectedRow == 0, let value = calculation.copyValue else { return }
|
||||
let pasteboard = NSPasteboard.general
|
||||
@@ -749,7 +744,14 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
|
||||
}
|
||||
|
||||
private func openApplication(_ application: Application) {
|
||||
guard !isLaunching else { return }
|
||||
guard !isLaunching, translation == nil, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return }
|
||||
if let command = application.command {
|
||||
switch command {
|
||||
case .translate: showTranslation()
|
||||
}
|
||||
history.recordLaunch(of: application)
|
||||
return
|
||||
}
|
||||
(window as? LauncherPanel)?.resetNumberHints()
|
||||
isLaunching = true
|
||||
updateOpenButton()
|
||||
|
||||
Reference in New Issue
Block a user