From 6cbad9abf2f8b00775760ee9a2251a15ab5f1a27 Mon Sep 17 00:00:00 2001 From: tuscias Date: Mon, 2 Feb 2026 21:40:21 +0200 Subject: [PATCH 1/2] feature: insert value in url and command by user via prompt --- Leader Key/Controller.swift | 52 ++++++- Leader Key/InputPromptWindow.swift | 143 ++++++++++++++++++ Leader Key/UserConfig.swift | 16 +- .../Views/ConfigOutlineEditorView.swift | 51 ++++++- Leader KeyTests/UserConfigTests.swift | 36 +++++ 5 files changed, 280 insertions(+), 18 deletions(-) create mode 100644 Leader Key/InputPromptWindow.swift diff --git a/Leader Key/Controller.swift b/Leader Key/Controller.swift index 37c0eecd..6d812553 100644 --- a/Leader Key/Controller.swift +++ b/Leader Key/Controller.swift @@ -22,6 +22,7 @@ class Controller { var window: MainWindow! var cheatsheetWindow: NSWindow! private var cheatsheetTimer: Timer? + private var inputPromptWindow: InputPromptWindow? private var cancellables = Set() @@ -160,7 +161,11 @@ class Controller { switch hit { case .action(let action): if execute { - if let mods = modifiers, isInStickyMode(mods) { + if action.requiresInput { + hide { + self.showInputPrompt(for: action) + } + } else if let mods = modifiers, isInStickyMode(mods) { runAction(action) } else { hide { @@ -300,17 +305,21 @@ class Controller { } private func runAction(_ action: Action) { + runActionWithValue(action, value: action.value) + } + + private func runActionWithValue(_ action: Action, value: String) { switch action.type { case .application: NSWorkspace.shared.openApplication( - at: URL(fileURLWithPath: action.value), + at: URL(fileURLWithPath: value), configuration: NSWorkspace.OpenConfiguration()) case .url: - openURL(action) + openURLWithValue(action, value: value) case .command: - CommandRunner.run(action.value) + CommandRunner.run(value) case .folder: - let path: String = (action.value as NSString).expandingTildeInPath + let path: String = (value as NSString).expandingTildeInPath NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: path) default: print("\(action.type) unknown") @@ -321,14 +330,41 @@ class Controller { } } + private func showInputPrompt(for action: Action) { + guard let promptMessage = action.prompt else { return } + + inputPromptWindow?.close() + inputPromptWindow = nil + + inputPromptWindow = InputPromptWindow( + label: action.displayName, + prompt: promptMessage, + placeholder: "", + onSubmit: { [weak self] input in + guard let self = self else { return } + let substitutedValue = action.valueWithInput(input) + self.runActionWithValue(action, value: substitutedValue) + self.inputPromptWindow = nil + }, + onCancel: { [weak self] in + self?.inputPromptWindow = nil + } + ) + inputPromptWindow?.showAndFocus() + } + private func clear() { userState.clear() } private func openURL(_ action: Action) { - guard let url = URL(string: action.value) else { + openURLWithValue(action, value: action.value) + } + + private func openURLWithValue(_ action: Action, value: String) { + guard let url = URL(string: value) else { showAlert( - title: "Invalid URL", message: "Failed to parse URL: \(action.value)") + title: "Invalid URL", message: "Failed to parse URL: \(value)") return } @@ -336,7 +372,7 @@ class Controller { showAlert( title: "Invalid URL", message: - "URL is missing protocol (e.g. https://, raycast://): \(action.value)" + "URL is missing protocol (e.g. https://, raycast://): \(value)" ) return } diff --git a/Leader Key/InputPromptWindow.swift b/Leader Key/InputPromptWindow.swift new file mode 100644 index 00000000..2beb44fd --- /dev/null +++ b/Leader Key/InputPromptWindow.swift @@ -0,0 +1,143 @@ +import Cocoa +import SwiftUI + +class InputPromptWindow: NSPanel { + private var textField: NSTextField! + private var onSubmit: ((String) -> Void)? + private var onCancel: (() -> Void)? + private var previousApp: NSRunningApplication? + + init(label: String, prompt: String, placeholder: String = "", onSubmit: @escaping (String) -> Void, onCancel: @escaping () -> Void) { + self.onSubmit = onSubmit + self.onCancel = onCancel + + super.init( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 56), + styleMask: [.borderless, .nonactivatingPanel], + backing: .buffered, + defer: false + ) + + self.isFloatingPanel = true + self.level = .screenSaver // Topmost level + self.isOpaque = false + self.backgroundColor = .clear + self.hasShadow = true + self.isReleasedWhenClosed = false + self.hidesOnDeactivate = false + self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] + + setupUI(label: label, prompt: prompt, placeholder: placeholder) + positionWindow() + + self.delegate = self + } + + override var canBecomeKey: Bool { true } + override var canBecomeMain: Bool { true } + + private func positionWindow() { + guard let screen = NSScreen.main else { + center() + return + } + + let screenFrame = screen.visibleFrame + let windowFrame = self.frame + let x = screenFrame.midX - windowFrame.width / 2 + let y = screenFrame.maxY - 200 - windowFrame.height // 200pt from top + + self.setFrameOrigin(NSPoint(x: x, y: y)) + } + + private func setupUI(label: String, prompt: String, placeholder: String) { + let container = NSView(frame: NSRect(x: 0, y: 0, width: 500, height: 56)) + container.wantsLayer = true + container.layer?.backgroundColor = NSColor.clear.cgColor + + let visualEffect = NSVisualEffectView(frame: container.bounds) + visualEffect.material = .hudWindow + visualEffect.state = .active + visualEffect.wantsLayer = true + visualEffect.layer?.cornerRadius = 12 + visualEffect.layer?.masksToBounds = true + container.addSubview(visualEffect) + + let actionLabel = NSTextField(labelWithString: label) + actionLabel.font = NSFont.systemFont(ofSize: 11, weight: .medium) + actionLabel.textColor = NSColor.tertiaryLabelColor + actionLabel.alignment = .right + actionLabel.frame = NSRect(x: 350, y: 36, width: 134, height: 14) + actionLabel.lineBreakMode = .byTruncatingTail + visualEffect.addSubview(actionLabel) + + textField = NSTextField(string: "") + textField.placeholderString = prompt + textField.font = NSFont.systemFont(ofSize: 24, weight: .light) + textField.isBordered = false + textField.drawsBackground = false + textField.focusRingType = .none + textField.frame = NSRect(x: 16, y: 10, width: 468, height: 32) + textField.delegate = self + visualEffect.addSubview(textField) + + self.contentView = container + } + + func showAndFocus() { + previousApp = NSWorkspace.shared.frontmostApplication + + positionWindow() + makeKeyAndOrderFront(nil) + orderFrontRegardless() // Force to front + textField.becomeFirstResponder() + textField.selectText(nil) + + NSApp.activate(ignoringOtherApps: true) + } + + @objc private func submitAction() { + let value = textField.stringValue + close() + onSubmit?(value) + } + + @objc private func cancelAction() { + close() + restorePreviousApp() + onCancel?() + } + + private func restorePreviousApp() { + previousApp?.activate() + } +} + +extension InputPromptWindow: NSTextFieldDelegate { + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + if commandSelector == #selector(NSResponder.insertNewline(_:)) { + submitAction() + return true + } else if commandSelector == #selector(NSResponder.cancelOperation(_:)) { + cancelAction() + return true + } + return false + } +} + +extension InputPromptWindow: NSWindowDelegate { + func windowDidResignKey(_ notification: Notification) { + cancelAction() + } +} + +extension Action { + var requiresInput: Bool { + return prompt != nil && !prompt!.isEmpty + } + + func valueWithInput(_ input: String) -> String { + return value.replacingOccurrences(of: "{input}", with: input) + } +} diff --git a/Leader Key/UserConfig.swift b/Leader Key/UserConfig.swift index c339d028..101ccb39 100644 --- a/Leader Key/UserConfig.swift +++ b/Leader Key/UserConfig.swift @@ -425,6 +425,7 @@ struct Action: Item, Codable, Equatable { var label: String? var value: String var iconPath: String? + var prompt: String? var displayName: String { guard let labelValue = label else { return bestGuessDisplayName } @@ -447,11 +448,11 @@ struct Action: Item, Codable, Equatable { return value } } - private enum CodingKeys: String, CodingKey { case key, type, label, value, iconPath } + private enum CodingKeys: String, CodingKey { case key, type, label, value, iconPath, prompt } init( uiid: UUID = UUID(), key: String?, type: Type, label: String? = nil, value: String, - iconPath: String? = nil + iconPath: String? = nil, prompt: String? = nil ) { self.uiid = uiid self.key = key @@ -459,6 +460,7 @@ struct Action: Item, Codable, Equatable { self.label = label self.value = value self.iconPath = iconPath + self.prompt = prompt } init(from decoder: Decoder) throws { @@ -469,6 +471,7 @@ struct Action: Item, Codable, Equatable { self.label = try c.decodeIfPresent(String.self, forKey: .label) self.value = try c.decode(String.self, forKey: .value) self.iconPath = try c.decodeIfPresent(String.self, forKey: .iconPath) + self.prompt = try c.decodeIfPresent(String.self, forKey: .prompt) } func encode(to encoder: Encoder) throws { @@ -482,6 +485,7 @@ struct Action: Item, Codable, Equatable { try c.encode(value, forKey: .value) if let l = label, !l.isEmpty { try c.encode(l, forKey: .label) } try c.encodeIfPresent(iconPath, forKey: .iconPath) + if let p = prompt, !p.isEmpty { try c.encode(p, forKey: .prompt) } } } @@ -555,7 +559,7 @@ enum ActionOrGroup: Codable, Equatable { } private enum CodingKeys: String, CodingKey { - case key, type, value, actions, label, iconPath + case key, type, value, actions, label, iconPath, prompt } var uiid: UUID { @@ -578,7 +582,8 @@ enum ActionOrGroup: Codable, Equatable { self = .group(Group(key: key, label: label, iconPath: iconPath, actions: actions)) default: let value = try container.decode(String.self, forKey: .value) - self = .action(Action(key: key, type: type, label: label, value: value, iconPath: iconPath)) + let prompt = try container.decodeIfPresent(String.self, forKey: .prompt) + self = .action(Action(key: key, type: type, label: label, value: value, iconPath: iconPath, prompt: prompt)) } } @@ -599,6 +604,9 @@ enum ActionOrGroup: Codable, Equatable { try container.encodeIfPresent(action.label, forKey: .label) } try container.encodeIfPresent(action.iconPath, forKey: .iconPath) + if action.prompt != nil && !action.prompt!.isEmpty { + try container.encodeIfPresent(action.prompt, forKey: .prompt) + } case .group(let group): // Always encode key in textual form for JSON if let keyValue = group.key { diff --git a/Leader Key/Views/ConfigOutlineEditorView.swift b/Leader Key/Views/ConfigOutlineEditorView.swift index 5838cc5b..c41554eb 100644 --- a/Leader Key/Views/ConfigOutlineEditorView.swift +++ b/Leader Key/Views/ConfigOutlineEditorView.swift @@ -513,12 +513,14 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { static let labelWidth: CGFloat = 160 static let iconButtonWidth: CGFloat = 28 static let iconSize: CGFloat = 24 + static let promptWidth: CGFloat = 80 } private var keyButton = NSButton() private var typePopup = NSPopUpButton() private var iconButton = NSButton() private var valueStack = NSStackView() private var labelButton = NSButton() + private var promptButton = NSButton() private var moreBtn = NSButton() private var onChange: ((EditorPayload) -> Void)? @@ -559,6 +561,14 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { minConstraint.priority = .required minConstraint.isActive = true } + promptButton.bezelStyle = .rounded + promptButton.controlSize = .regular + promptButton.toolTip = "Set a prompt for user input. Use {input} in the value as placeholder." + do { + let minConstraint = promptButton.widthAnchor.constraint(greaterThanOrEqualToConstant: Layout.promptWidth) + minConstraint.priority = .required + minConstraint.isActive = true + } moreBtn.bezelStyle = .rounded moreBtn.controlSize = .regular moreBtn.image = NSImage(systemSymbolName: "ellipsis.circle", accessibilityDescription: nil) @@ -569,13 +579,13 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { iconButton.imageScaling = .scaleProportionallyDown iconButton.widthAnchor.constraint(equalToConstant: Layout.iconButtonWidth).isActive = true - for view in [keyButton, typePopup, iconButton, labelButton, moreBtn] { + for view in [keyButton, typePopup, iconButton, labelButton, promptButton, moreBtn] { view.makeRigid() } valueStack.makeFlex() - for view in [keyButton, typePopup, iconButton, valueStack, labelButton, moreBtn] { + for view in [keyButton, typePopup, iconButton, valueStack, labelButton, promptButton, moreBtn] { container.addArrangedSubview(view) } addSubview(container) @@ -600,6 +610,19 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { self.updateButtons(for: a) } } + promptButton.targetClosure { [weak self] in + guard let self else { return } + self.promptText( + title: "Prompt", + message: "Enter the prompt message shown to users.\nUse {input} in the value field as a placeholder for user input.", + initial: self.currentAction()?.prompt ?? "" + ) { text in + guard var a = self.currentAction() else { return } + a.prompt = text.isEmpty ? nil : text + self.onChange?(.action(a)) + self.updateButtons(for: a) + } + } moreBtn.targetClosure { [weak self] in self?.showMoreMenu(anchor: self?.moreBtn) } } @@ -639,11 +662,19 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { keyButton.title = (action.key?.isEmpty ?? true) ? "Key" : (KeyMaps.glyph(for: action.key ?? "") ?? action.key ?? "Key") - let isPlaceholder = (action.label?.isEmpty ?? true) + let isLabelPlaceholder = (action.label?.isEmpty ?? true) ConfigEditorUI.setButtonTitle( labelButton, - text: isPlaceholder ? "Label" : (action.label ?? "Label"), - placeholder: isPlaceholder) + text: isLabelPlaceholder ? "Label" : (action.label ?? "Label"), + placeholder: isLabelPlaceholder) + + let isPromptPlaceholder = (action.prompt?.isEmpty ?? true) + ConfigEditorUI.setButtonTitle( + promptButton, + text: isPromptPlaceholder ? "Prompt" : (action.prompt ?? "Prompt"), + placeholder: isPromptPlaceholder) + + promptButton.isHidden = !(action.type == .url || action.type == .command) } private func updateValidationStyle(_ error: ValidationErrorType?) { @@ -712,6 +743,7 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { guard let action = currentAction() else { return } onChange?(.action(action)) rebuildValue(for: action) // ensure value UI matches type after change + updateButtons(for: action) // ensure prompt button visibility updates when type changes } private struct ValueDescriptor { @@ -828,12 +860,19 @@ private class ActionCellView: NSTableCellView, NSWindowDelegate { } private func promptText(title: String, initial: String, onOK: @escaping (String) -> Void) { + promptText(title: title, message: nil, initial: initial, onOK: onOK) + } + + private func promptText(title: String, message: String?, initial: String, onOK: @escaping (String) -> Void) { let alert = NSAlert() alert.messageText = title + if let message = message { + alert.informativeText = message + } alert.addButton(withTitle: "OK") alert.addButton(withTitle: "Cancel") let field = NSTextField(string: initial) - field.frame = NSRect(x: 0, y: 0, width: 260, height: 22) + field.frame = NSRect(x: 0, y: 0, width: 300, height: 22) alert.accessoryView = field // Focus and select all when the dialog opens alert.window.initialFirstResponder = field diff --git a/Leader KeyTests/UserConfigTests.swift b/Leader KeyTests/UserConfigTests.swift index 6a8fb64f..78a6b394 100644 --- a/Leader KeyTests/UserConfigTests.swift +++ b/Leader KeyTests/UserConfigTests.swift @@ -142,6 +142,42 @@ final class UserConfigTests: XCTestCase { XCTAssertEqual(testAlertManager.shownAlerts.count, 0) } + func testActionDecodesPrompt() throws { + let json = """ + { + "key": "f", + "type": "url", + "label": "Example API", + "value": "https://api.example.com/{input}", + "prompt": "Enter Id:" + } + """ + + let data = try XCTUnwrap(json.data(using: .utf8)) + let action = try JSONDecoder().decode(Action.self, from: data) + + XCTAssertEqual(action.prompt, "Enter Id:") + } + + func testActionEncodesPrompt() throws { + let action = Action( + key: "f", + type: .url, + label: "Example API", + value: "https://api.example.com/{input}", + iconPath: nil, + prompt: "Enter Id:" + ) + + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys] + let data = try encoder.encode(action) + let json = try XCTUnwrap(String(data: data, encoding: .utf8)) + + XCTAssertTrue(json.contains("\"prompt\"")) + XCTAssertTrue(json.contains("Enter Id:")) + } + private func waitForConfigLoad() { let expectation = expectation(description: "config load flush") DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { From 15b08e89791dc14848d8247d88b3345d9bf064bf Mon Sep 17 00:00:00 2001 From: tuscias Date: Mon, 2 Feb 2026 21:43:07 +0200 Subject: [PATCH 2/2] add missing file --- Leader Key.xcodeproj/project.pbxproj | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Leader Key.xcodeproj/project.pbxproj b/Leader Key.xcodeproj/project.pbxproj index 141cebf6..e88a423c 100644 --- a/Leader Key.xcodeproj/project.pbxproj +++ b/Leader Key.xcodeproj/project.pbxproj @@ -15,7 +15,6 @@ 423632282D6A806700878D92 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423632272D6A806700878D92 /* Theme.swift */; }; 42454DDB2D71CB39004E1374 /* ConfigValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42454DDA2D71CB39004E1374 /* ConfigValidator.swift */; }; 42454DDD2D71CBAB004E1374 /* ConfigValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42454DDC2D71CBAB004E1374 /* ConfigValidatorTests.swift */; }; - EC5CEBC4C47B4C5DB2258813 /* URLSchemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5CEBC4C47B4C5DB2258814 /* URLSchemeTests.swift */; }; 425495402D75EFAD0020300E /* ForTheHorde.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4254953F2D75EFAD0020300E /* ForTheHorde.swift */; }; 426E625B2D2E6A98009FD2F2 /* CommandRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 426E625A2D2E6A98009FD2F2 /* CommandRunner.swift */; }; 4279AFED2C6A175500952A83 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 4279AFEC2C6A175500952A83 /* LaunchAtLogin */; }; @@ -26,7 +25,6 @@ 427C181A2BD3123C00955B98 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 427C18192BD3123C00955B98 /* Defaults */; }; 427C181C2BD314B500955B98 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C181B2BD314B500955B98 /* Constants.swift */; }; 427C18202BD31C3D00955B98 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C181F2BD31C3D00955B98 /* AppDelegate.swift */; }; - 73192AF63CAF425397D7C0D1 /* URLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73192AF63CAF425397D7C0D2 /* URLSchemeHandler.swift */; }; 427C18232BD31DF100955B98 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = 427C18222BD31DF100955B98 /* Settings */; }; 427C18282BD31E2E00955B98 /* GeneralPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C18242BD31E2E00955B98 /* GeneralPane.swift */; }; 427C18292BD31E2E00955B98 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 427C18252BD31E2E00955B98 /* MainWindow.swift */; }; @@ -55,6 +53,9 @@ 6D9B9C012DBA000000000001 /* ConfigOutlineEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9B9C002DBA000000000001 /* ConfigOutlineEditorView.swift */; }; 6D9B9C042DBA000000000002 /* KeyCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9B9C032DBA000000000002 /* KeyCapture.swift */; }; 6D9B9C062DBA000000000003 /* ConfigEditorShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9B9C052DBA000000000003 /* ConfigEditorShared.swift */; }; + 73192AF63CAF425397D7C0D1 /* URLSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73192AF63CAF425397D7C0D2 /* URLSchemeHandler.swift */; }; + ABCD12342E850000DEADBEEF /* InputPromptWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABCD12332E850000DEADBEEF /* InputPromptWindow.swift */; }; + EC5CEBC4C47B4C5DB2258813 /* URLSchemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5CEBC4C47B4C5DB2258814 /* URLSchemeTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,7 +77,6 @@ 423632272D6A806700878D92 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 42454DDA2D71CB39004E1374 /* ConfigValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigValidator.swift; sourceTree = ""; }; 42454DDC2D71CBAB004E1374 /* ConfigValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigValidatorTests.swift; sourceTree = ""; }; - EC5CEBC4C47B4C5DB2258814 /* URLSchemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSchemeTests.swift; sourceTree = ""; }; 4254953F2D75EFAD0020300E /* ForTheHorde.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForTheHorde.swift; sourceTree = ""; }; 426E625A2D2E6A98009FD2F2 /* CommandRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandRunner.swift; sourceTree = ""; }; 4279AFEA2C6A08B100952A83 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "Leader Key/Support/Info.plist"; sourceTree = SOURCE_ROOT; }; @@ -88,7 +88,6 @@ 427C17FC2BD311B500955B98 /* UserConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfigTests.swift; sourceTree = ""; }; 427C181B2BD314B500955B98 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 427C181F2BD31C3D00955B98 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 73192AF63CAF425397D7C0D2 /* URLSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSchemeHandler.swift; sourceTree = ""; }; 427C18242BD31E2E00955B98 /* GeneralPane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPane.swift; sourceTree = ""; }; 427C18252BD31E2E00955B98 /* MainWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = ""; }; 427C18262BD31E2E00955B98 /* StatusItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusItem.swift; sourceTree = ""; }; @@ -114,6 +113,9 @@ 6D9B9C002DBA000000000001 /* ConfigOutlineEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigOutlineEditorView.swift; sourceTree = ""; }; 6D9B9C032DBA000000000002 /* KeyCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCapture.swift; sourceTree = ""; }; 6D9B9C052DBA000000000003 /* ConfigEditorShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigEditorShared.swift; sourceTree = ""; }; + 73192AF63CAF425397D7C0D2 /* URLSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSchemeHandler.swift; sourceTree = ""; }; + ABCD12332E850000DEADBEEF /* InputPromptWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputPromptWindow.swift; sourceTree = ""; }; + EC5CEBC4C47B4C5DB2258814 /* URLSchemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSchemeTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -195,6 +197,7 @@ 427C184C2BD65C5C00955B98 /* Defaults.swift */, 42DFCD712D5B7D46002EA111 /* Events.swift */, 42F4CDD02D48C51F00D0DD76 /* Extensions.swift */, + ABCD12332E850000DEADBEEF /* InputPromptWindow.swift */, 42F4CDC82D458FF700D0DD76 /* MainMenu.swift */, 427C18252BD31E2E00955B98 /* MainWindow.swift */, 427C18532BD6E59300955B98 /* NSWindow+Animations.swift */, @@ -423,6 +426,7 @@ 42DFCD722D5B7D48002EA111 /* Events.swift in Sources */, 427C182D2BD31F9500955B98 /* Settings.swift in Sources */, 426E625B2D2E6A98009FD2F2 /* CommandRunner.swift in Sources */, + ABCD12342E850000DEADBEEF /* InputPromptWindow.swift in Sources */, 427C18382BD3262100955B98 /* VisualEffectBackground.swift in Sources */, 42B21FBC2D67566100F4A2C7 /* Alerts.swift in Sources */, 427C183B2BD329F900955B98 /* UserConfig.swift in Sources */,