diff options
Diffstat (limited to 'menubar/search.swift')
| -rw-r--r-- | menubar/search.swift | 40 |
1 files changed, 32 insertions, 8 deletions
diff --git a/menubar/search.swift b/menubar/search.swift index 2bc044a..c62c612 100644 --- a/menubar/search.swift +++ b/menubar/search.swift @@ -157,9 +157,12 @@ struct SearchState: Equatable { offset = max(0, min(offset, max(0, results.count - Self.visibleRows))) } + var selectedRow: Row? { results.first { $0.row.paneId == selected }?.row } + var footer: String { let count = results.count == rows.count ? "\(rows.count) agents" : "\(results.count) of \(rows.count)" - return "\(count) ↑↓ select ↩ jump esc close" + let unread = selectedRow?.canMarkUnread == true ? " ⌘U mark unread" : "" + return "\(count) ↑↓ select ↩ jump\(unread) esc close" } /// Printed by --print-search, and compared to skip needless re-renders. @@ -213,12 +216,14 @@ final class SearchController: NSObject, NSWindowDelegate, NSTextFieldDelegate { private let field = NSTextField() private let list = NSStackView() private let onJump: (String) -> Void + private let onMarkUnread: (String) -> Void private var state = SearchState(rows: []) private var rendered: String? private var keyMonitor: Any? - init(onJump: @escaping (String) -> Void) { + init(onJump: @escaping (String) -> Void, onMarkUnread: @escaping (String) -> Void = { _ in }) { self.onJump = onJump + self.onMarkUnread = onMarkUnread panel = KeyPanel(contentRect: NSRect(x: 0, y: 0, width: searchWidth, height: 80), styleMask: [.nonactivatingPanel, .borderless], backing: .buffered, defer: false) super.init() @@ -321,6 +326,14 @@ final class SearchController: NSObject, NSWindowDelegate, NSTextFieldDelegate { onJump(paneId) } + /// ⌘U: mark the selected agent unread; search stays open, and the agent + /// moves to the done ones as the list refreshes. + private func markSelectionUnread() { + guard let row = state.selectedRow, row.canMarkUnread else { NSSound.beep(); return } + log("search: mark \(row.paneId) unread") + onMarkUnread(row.paneId) + } + /// Debug: render `query` over `rows` offscreen to a PNG. func snapshot(rows: [Row], query: String, down: Int, to path: String) -> Bool { state = SearchState(rows: rows) @@ -335,12 +348,17 @@ final class SearchController: NSObject, NSWindowDelegate, NSTextFieldDelegate { // MARK: keys /// ⌃N ⌃P ⌃J ⌃K move, ⌃U clears: fzf's keys, before the text field's own - /// Emacs bindings see them. + /// Emacs bindings see them. ⌘U marks the selected agent unread. private func installKeyMonitor() { guard keyMonitor == nil else { return } keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in - guard let self, event.window === self.panel, - event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .control else { return event } + guard let self, event.window === self.panel else { return event } + let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) + if flags == .command, event.charactersIgnoringModifiers == "u" { + self.markSelectionUnread() + return nil + } + guard flags == .control else { return event } switch event.charactersIgnoringModifiers { case "n", "j": self.move(1) case "p", "k": self.move(-1) @@ -392,9 +410,9 @@ final class SearchController: NSObject, NSWindowDelegate, NSTextFieldDelegate { } for match in state.visible { let view = PanelRowView(row: match.row, title: highlightedTitle(match), - selected: match.row.paneId == state.selected) { [weak self] paneId in - self?.jump(to: paneId) - } + selected: match.row.paneId == state.selected, + onClick: { [weak self] paneId in self?.jump(to: paneId) }, + onMarkUnread: { [weak self] paneId in self?.onMarkUnread(paneId) }) list.addArrangedSubview(view) view.widthAnchor.constraint(equalTo: list.widthAnchor).isActive = true } @@ -488,5 +506,11 @@ func searchSelftest() -> [String] { scroll.move(-12) check(scroll.offset == 0, "state: and back to the top") check(scroll.footer.hasPrefix("25 agents"), "footer: agent count") + let mixed = SearchState(rows: [row("%1", "a", .idle), row("%2", "b", .done)]) + var onDone = mixed + onDone.move(1) + check(mixed.selectedRow?.paneId == "%1" && mixed.footer.contains("⌘U mark unread"), + "footer: ⌘U offered for an idle agent") + check(onDone.selectedRow?.paneId == "%2" && !onDone.footer.contains("⌘U"), "footer: not for a done one") return failures } |
