aboutsummaryrefslogtreecommitdiff
path: root/menubar/main.swift
diff options
context:
space:
mode:
Diffstat (limited to 'menubar/main.swift')
-rw-r--r--menubar/main.swift57
1 files changed, 52 insertions, 5 deletions
diff --git a/menubar/main.swift b/menubar/main.swift
index 183c5db..a00288c 100644
--- a/menubar/main.swift
+++ b/menubar/main.swift
@@ -1,6 +1,7 @@
// dingger menu bar: herdr's agent panel as a macOS status item, plus a
-// floating always-on-top panel (panel.swift) toggled by a global shortcut
-// (hotkey.swift). Rows and colors come from model.swift.
+// floating always-on-top panel (panel.swift) and agent search (search.swift),
+// each on a global shortcut (hotkey.swift). Rows and colors come from
+// model.swift.
//
// Reads the pane options the dingger daemon writes and renders herdr's
// Symbols style with herdr's palette:
@@ -19,8 +20,12 @@
// --snapshot-panel <png> [--full]
// render the panel offscreen (after a different render, to
// catch stale content) over white and black, then exit
+// --print-search <query>
+// print what agent search would list for <query>, then exit
+// --snapshot-search <png> <query> [<down>]
+// render agent search offscreen, then exit
// --click <pane> perform a row click for <pane>, then exit
-// --selftest check the panel visibility rules, then exit
+// --selftest check the panel and search rules, then exit
import AppKit
@@ -32,6 +37,7 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
private let menu = NSMenu()
private var panel: PanelController!
+ private var searchWindow: SearchController!
private var visibility = Menubar.loadVisibility()
private var settings = Settings()
private var settingsModified: Date?
@@ -39,6 +45,8 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
private var hotkeyRegistered = false
private var jumpHotkey: Hotkey?
private var jumpHotkeyRegistered = false
+ private var searchHotkey: Hotkey?
+ private var searchHotkeyRegistered = false
private var prefersFull = UserDefaults.standard.bool(forKey: "panelFull")
private var rows: [Row] = []
@@ -64,6 +72,7 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
panel = PanelController(
onClick: { [weak self] paneId in self?.jump(to: paneId) },
onToggleFull: { [weak self] in self?.toggleFull() })
+ searchWindow = SearchController(onJump: { [weak self] paneId in self?.jump(to: paneId) })
Settings.ensureFile()
reloadSettings()
refresh()
@@ -82,6 +91,9 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
(jumpHotkey, jumpHotkeyRegistered) = registerShortcut(.jump, settings.jumpHotkey, "jump") {
[weak self] in self?.jumpToTopWaiting()
}
+ (searchHotkey, searchHotkeyRegistered) = registerShortcut(.search, settings.searchHotkey, "search") {
+ [weak self] in self?.toggleSearch()
+ }
}
private func registerShortcut(_ id: GlobalHotkey.Id, _ spec: String, _ name: String,
@@ -113,6 +125,17 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
visibility.anyWaiting = rows.contains(where: \.waiting)
applyPanel()
+ searchWindow.update(rows: rows)
+ }
+
+ /// The search shortcut and menu item: open agent search, or close it.
+ @objc private func toggleSearch() {
+ if searchWindow.isOpen {
+ searchWindow.close("shortcut")
+ } else {
+ rows = fetchRows()
+ searchWindow.open(rows: rows)
+ }
}
private var panelContent: PanelContent {
@@ -229,6 +252,15 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
}
menu.addItem(jumpItem)
+ let searchItem = NSMenuItem(title: "Search Agents…", action: #selector(toggleSearch), keyEquivalent: "")
+ searchItem.target = self
+ searchItem.isEnabled = !rows.isEmpty
+ if let searchHotkey, searchHotkeyRegistered {
+ searchItem.keyEquivalent = searchHotkey.key.count == 1 ? searchHotkey.key : ""
+ searchItem.keyEquivalentModifierMask = searchHotkey.modifierFlags
+ }
+ menu.addItem(searchItem)
+
let policyMenu = NSMenu()
for policy in PanelPolicy.allCases {
let option = NSMenuItem(title: policy.label, action: #selector(selectPolicy(_:)), keyEquivalent: "")
@@ -242,7 +274,8 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
menu.addItem(policyItem)
for (shortcut, registered, spec) in [(hotkey, hotkeyRegistered, settings.hotkey),
- (jumpHotkey, jumpHotkeyRegistered, settings.jumpHotkey)] {
+ (jumpHotkey, jumpHotkeyRegistered, settings.jumpHotkey),
+ (searchHotkey, searchHotkeyRegistered, settings.searchHotkey)] {
guard !registered else { continue }
let problem = shortcut.map { "Shortcut \($0.display) is used by another app" }
?? "Shortcut \"\(spec)\" is not valid"
@@ -267,12 +300,13 @@ final class Menubar: NSObject, NSApplicationDelegate, NSMenuDelegate {
let arguments = CommandLine.arguments
if arguments.contains("--selftest") {
- var failures = panelSelftest()
+ var failures = panelSelftest() + searchSelftest()
if Hotkey.parse("ctrl+opt+cmd+e")?.display != "⌃⌥⌘E" { failures.append("hotkey: parse ctrl+opt+cmd+e") }
if Hotkey.parse("e") != nil { failures.append("hotkey: a key without modifiers is rejected") }
if Hotkey.parse("ctrl+opt+bogus") != nil { failures.append("hotkey: unknown keys are rejected") }
if Hotkey.parse("cmd+shift+f12")?.display != "⇧⌘F12" { failures.append("hotkey: parse cmd+shift+f12") }
if Hotkey.parse(Settings().jumpHotkey)?.display != "⌃⌥⌘U" { failures.append("hotkey: default jump is ⌃⌥⌘U") }
+ if Hotkey.parse(Settings().searchHotkey)?.display != "⌃⌥⇧⌘U" { failures.append("hotkey: default search is ⌃⌥⇧⌘U") }
failures.forEach { print("FAIL \($0)") }
print(failures.isEmpty ? "selftest ok" : "selftest: \(failures.count) failed")
exit(failures.isEmpty ? 0 : 1)
@@ -301,6 +335,19 @@ if let index = arguments.firstIndex(of: "--snapshot-panel"), index + 1 < argumen
controller.render(PanelContent(rows: fetchRows(), full: full, canToggle: full))
exit(controller.snapshot(to: arguments[index + 1]) ? 0 : 1)
}
+if let index = arguments.firstIndex(of: "--print-search") {
+ var state = SearchState(rows: fetchRows())
+ state.setQuery(index + 1 < arguments.count ? arguments[index + 1] : "")
+ print(state.plainText)
+ exit(0)
+}
+if let index = arguments.firstIndex(of: "--snapshot-search"), index + 2 < arguments.count {
+ _ = NSApplication.shared
+ let controller = SearchController(onJump: { _ in })
+ let down = index + 3 < arguments.count ? Int(arguments[index + 3]) ?? 0 : 0
+ exit(controller.snapshot(rows: fetchRows(), query: arguments[index + 2], down: down,
+ to: arguments[index + 1]) ? 0 : 1)
+}
if let index = arguments.firstIndex(of: "--click"), index + 1 < arguments.count {
focus(arguments[index + 1])
exit(0)