diff --git a/README.md b/README.md index 522ef28..89ca78c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Open `Package.swift` in Xcode to edit the project. Use the bundled app above to ## Controls - Type to filter applications; the first match is selected automatically. -- Up/Down changes the selection without leaving search. +- Up/Down or Control-K/Control-J changes the selection without leaving search or modifying its text. - Return, keypad Enter, the Open Application button, or a double-click opens the selection. - Escape, Command-W, or switching focus away dismisses the launcher. - Reopen from Finder or the Dock to show it again with an empty search. Dismissal leaves the process running; Command-Q quits it. diff --git a/Sources/Conjure/LauncherWindowController.swift b/Sources/Conjure/LauncherWindowController.swift index 02cf2c1..c4c1c4e 100644 --- a/Sources/Conjure/LauncherWindowController.swift +++ b/Sources/Conjure/LauncherWindowController.swift @@ -32,6 +32,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe super.init(window: window) configureContent(in: window) window.delegate = self + window.navigateSelection = { [weak self] offset in self?.moveSelection(by: offset) } } required init?(coder: NSCoder) { @@ -390,6 +391,24 @@ private final class ApplicationTableView: NSTableView { private final class LauncherPanel: NSPanel { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { true } + var navigateSelection: ((Int) -> Void)? + + override func sendEvent(_ event: NSEvent) { + if event.type == .keyDown, attachedSheet == nil, + event.modifierFlags.intersection([.control, .command, .option, .shift]) == .control { + switch event.charactersIgnoringModifiers?.lowercased() { + case "j": + navigateSelection?(1) + return + case "k": + navigateSelection?(-1) + return + default: + break + } + } + super.sendEvent(event) + } override func performClose(_ sender: Any?) { close()