Skip to content

Commit 3aa25f8

Browse files
committed
Add Xcode inspector debug menu
1 parent 24cb78d commit 3aa25f8

File tree

5 files changed

+167
-57
lines changed

5 files changed

+167
-57
lines changed

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
C87B03AC293B2CF300C77EAE /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C81458902939EFDC00135263 /* XcodeKit.framework */; };
3939
C87B03AD293B2CF300C77EAE /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C81458902939EFDC00135263 /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4040
C882175C294187EF00A22FD3 /* Client in Frameworks */ = {isa = PBXBuildFile; productRef = C882175B294187EF00A22FD3 /* Client */; };
41+
C89E75C32A46FB32000DD64F /* AppDelegate+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89E75C22A46FB32000DD64F /* AppDelegate+Menu.swift */; };
4142
C8A3AE592A2885A70046E809 /* InitializePython.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A3AE582A2885A70046E809 /* InitializePython.swift */; };
4243
C8C8B60929AFA35F00034BEE /* CopilotForXcodeExtensionService.app in Embed XPCService */ = {isa = PBXBuildFile; fileRef = C861E60E2994F6070056CB02 /* CopilotForXcodeExtensionService.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4344
C8DCF00029CE11D500FDDDD7 /* ChatWithSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DCEFFF29CE11D500FDDDD7 /* ChatWithSelection.swift */; };
@@ -166,6 +167,7 @@
166167
C87B03A8293B262600C77EAE /* NextSuggestionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextSuggestionCommand.swift; sourceTree = "<group>"; };
167168
C87B03AA293B262E00C77EAE /* PreviousSuggestionCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviousSuggestionCommand.swift; sourceTree = "<group>"; };
168169
C887BC832965D96000931567 /* DEVELOPMENT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPMENT.md; sourceTree = "<group>"; };
170+
C89E75C22A46FB32000DD64F /* AppDelegate+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Menu.swift"; sourceTree = "<group>"; };
169171
C8A3AE512A2883430046E809 /* Python.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Python.xcframework; sourceTree = "<group>"; };
170172
C8A3AE582A2885A70046E809 /* InitializePython.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializePython.swift; sourceTree = "<group>"; };
171173
C8A3AE5A2A288AF90046E809 /* site-packages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "site-packages"; sourceTree = "<group>"; };
@@ -322,6 +324,7 @@
322324
C81291D92994FE7900196E12 /* Info.plist */,
323325
C861E61F2994F6390056CB02 /* ServiceDelegate.swift */,
324326
C861E6102994F6070056CB02 /* AppDelegate.swift */,
327+
C89E75C22A46FB32000DD64F /* AppDelegate+Menu.swift */,
325328
C8A3AE582A2885A70046E809 /* InitializePython.swift */,
326329
C81291D52994FE6900196E12 /* Main.storyboard */,
327330
C861E6142994F6080056CB02 /* Assets.xcassets */,
@@ -583,6 +586,7 @@
583586
buildActionMask = 2147483647;
584587
files = (
585588
C8A3AE592A2885A70046E809 /* InitializePython.swift in Sources */,
589+
C89E75C32A46FB32000DD64F /* AppDelegate+Menu.swift in Sources */,
586590
C861E6202994F63A0056CB02 /* ServiceDelegate.swift in Sources */,
587591
C861E6112994F6070056CB02 /* AppDelegate.swift in Sources */,
588592
);

Core/Sources/HostApp/DebugView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ final class DebugSettings: ObservableObject {
1010
@AppStorage(\.triggerActionWithAccessibilityAPI) var triggerActionWithAccessibilityAPI
1111
@AppStorage(\.alwaysAcceptSuggestionWithAccessibilityAPI)
1212
var alwaysAcceptSuggestionWithAccessibilityAPI
13+
@AppStorage(\.enableXcodeInspectorDebugMenu) var enableXcodeInspectorDebugMenu
1314
init() {}
1415
}
1516

@@ -40,6 +41,9 @@ struct DebugSettingsView: View {
4041
Toggle(isOn: $settings.alwaysAcceptSuggestionWithAccessibilityAPI) {
4142
Text("Always accept suggestion with AccessibilityAPI")
4243
}
44+
Toggle(isOn: $settings.enableXcodeInspectorDebugMenu) {
45+
Text("Enable Xcode inspector debug menu")
46+
}
4347
}
4448
.padding()
4549
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import AppKit
2+
import Foundation
3+
import Preferences
4+
import XcodeInspector
5+
6+
extension AppDelegate {
7+
fileprivate var statusBarMenuIdentifier: NSUserInterfaceItemIdentifier {
8+
.init("statusBarMenu")
9+
}
10+
11+
fileprivate var xcodeInspectorDebugMenuIdentifier: NSUserInterfaceItemIdentifier {
12+
.init("xcodeInspectorDebugMenu")
13+
}
14+
15+
@objc func buildStatusBarMenu() {
16+
let statusBar = NSStatusBar.system
17+
statusBarItem = statusBar.statusItem(
18+
withLength: NSStatusItem.squareLength
19+
)
20+
statusBarItem.button?.image = NSImage(named: "MenuBarIcon")
21+
22+
let statusBarMenu = NSMenu(title: "Status Bar Menu")
23+
statusBarMenu.identifier = statusBarMenuIdentifier
24+
statusBarItem.menu = statusBarMenu
25+
26+
let hostAppName = Bundle.main.object(forInfoDictionaryKey: "HOST_APP_NAME") as? String
27+
?? "Copilot for Xcode"
28+
29+
let copilotName = NSMenuItem(
30+
title: hostAppName,
31+
action: nil,
32+
keyEquivalent: ""
33+
)
34+
35+
let checkForUpdate = NSMenuItem(
36+
title: "Check for Updates",
37+
action: #selector(checkForUpdate),
38+
keyEquivalent: ""
39+
)
40+
41+
let openCopilotForXcode = NSMenuItem(
42+
title: "Open \(hostAppName)",
43+
action: #selector(openCopilotForXcode),
44+
keyEquivalent: ""
45+
)
46+
47+
let openGlobalChat = NSMenuItem(
48+
title: "Open Chat",
49+
action: #selector(openGlobalChat),
50+
keyEquivalent: ""
51+
)
52+
53+
let xcodeInspectorDebug = NSMenuItem(
54+
title: "Xcode Inspector Debug",
55+
action: nil,
56+
keyEquivalent: ""
57+
)
58+
59+
let xcodeInspectorDebugMenu = NSMenu(title: "Xcode Inspector Debug")
60+
xcodeInspectorDebugMenu.identifier = xcodeInspectorDebugMenuIdentifier
61+
xcodeInspectorDebug.submenu = xcodeInspectorDebugMenu
62+
xcodeInspectorDebug.isHidden = false
63+
64+
let quitItem = NSMenuItem(
65+
title: "Quit",
66+
action: #selector(quit),
67+
keyEquivalent: ""
68+
)
69+
quitItem.target = self
70+
71+
statusBarMenu.addItem(copilotName)
72+
statusBarMenu.addItem(openCopilotForXcode)
73+
statusBarMenu.addItem(checkForUpdate)
74+
statusBarMenu.addItem(.separator())
75+
statusBarMenu.addItem(openGlobalChat)
76+
statusBarMenu.addItem(.separator())
77+
statusBarMenu.addItem(xcodeInspectorDebug)
78+
statusBarMenu.addItem(quitItem)
79+
80+
statusBarMenu.delegate = self
81+
xcodeInspectorDebugMenu.delegate = self
82+
}
83+
}
84+
85+
extension AppDelegate: NSMenuDelegate {
86+
func menuWillOpen(_ menu: NSMenu) {
87+
switch menu.identifier {
88+
case statusBarMenuIdentifier:
89+
if let xcodeInspectorDebug = menu.items.first(where: { item in
90+
item.submenu?.identifier == xcodeInspectorDebugMenuIdentifier
91+
}) {
92+
xcodeInspectorDebug.isHidden = !UserDefaults.shared
93+
.value(for: \.enableXcodeInspectorDebugMenu)
94+
}
95+
case xcodeInspectorDebugMenuIdentifier:
96+
let inspector = XcodeInspector.shared
97+
menu.items.removeAll()
98+
menu.items.append(.text("Active Project: \(inspector.activeProjectURL)"))
99+
menu.items.append(.text("Active Document: \(inspector.activeDocumentURL)"))
100+
for xcode in inspector.xcodes {
101+
let item = NSMenuItem(
102+
title: "Xcode \(xcode.runningApplication.processIdentifier)",
103+
action: nil,
104+
keyEquivalent: ""
105+
)
106+
menu.addItem(item)
107+
let xcodeMenu = NSMenu()
108+
item.submenu = xcodeMenu
109+
xcodeMenu.items.append(.text("Is Active: \(xcode.isActive)"))
110+
xcodeMenu.items.append(.text("Active Project: \(xcode.projectURL)"))
111+
xcodeMenu.items.append(.text("Active Document: \(xcode.documentURL)"))
112+
113+
for (key, workspace) in xcode.workspaces {
114+
let workspaceItem = NSMenuItem(
115+
title: "Workspace \(key)",
116+
action: nil,
117+
keyEquivalent: ""
118+
)
119+
xcodeMenu.items.append(workspaceItem)
120+
let workspaceMenu = NSMenu()
121+
workspaceItem.submenu = workspaceMenu
122+
let tabsItem = NSMenuItem(
123+
title: "Tabs",
124+
action: nil,
125+
keyEquivalent: ""
126+
)
127+
workspaceMenu.addItem(tabsItem)
128+
let tabsMenu = NSMenu()
129+
tabsItem.submenu = tabsMenu
130+
for tab in workspace.tabs {
131+
tabsMenu.addItem(.text(tab))
132+
}
133+
}
134+
}
135+
default:
136+
break
137+
}
138+
}
139+
}
140+
141+
private extension NSMenuItem {
142+
static func text(_ text: String) -> NSMenuItem {
143+
let item = NSMenuItem(
144+
title: text,
145+
action: nil,
146+
keyEquivalent: ""
147+
)
148+
item.isEnabled = false
149+
return item
150+
}
151+
}

ExtensionService/AppDelegate.swift

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import AppKit
1+
22
import Environment
33
import FileChangeChecker
44
import LaunchAgentManager
@@ -20,9 +20,9 @@ let serviceIdentifier = bundleIdentifierBase + ".ExtensionService"
2020
@main
2121
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
2222
let scheduledCleaner = ScheduledCleaner()
23-
private var statusBarItem: NSStatusItem!
24-
private var xpcListener: (NSXPCListener, ServiceDelegate)?
25-
private let updateChecker =
23+
var statusBarItem: NSStatusItem!
24+
var xpcListener: (NSXPCListener, ServiceDelegate)?
25+
let updateChecker =
2626
UpdateChecker(
2727
hostBundle: locateHostBundleURL(url: Bundle.main.bundleURL)
2828
.flatMap(Bundle.init(url:))
@@ -53,59 +53,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
5353
}
5454
}
5555

56-
@objc private func buildStatusBarMenu() {
57-
let statusBar = NSStatusBar.system
58-
statusBarItem = statusBar.statusItem(
59-
withLength: NSStatusItem.squareLength
60-
)
61-
statusBarItem.button?.image = NSImage(named: "MenuBarIcon")
62-
63-
let statusBarMenu = NSMenu(title: "Status Bar Menu")
64-
statusBarItem.menu = statusBarMenu
65-
66-
let hostAppName = Bundle.main.object(forInfoDictionaryKey: "HOST_APP_NAME") as? String
67-
?? "Copilot for Xcode"
68-
69-
let copilotName = NSMenuItem(
70-
title: hostAppName,
71-
action: nil,
72-
keyEquivalent: ""
73-
)
74-
75-
let checkForUpdate = NSMenuItem(
76-
title: "Check for Updates",
77-
action: #selector(checkForUpdate),
78-
keyEquivalent: ""
79-
)
80-
81-
let openCopilotForXcode = NSMenuItem(
82-
title: "Open \(hostAppName)",
83-
action: #selector(openCopilotForXcode),
84-
keyEquivalent: ""
85-
)
86-
87-
let openGlobalChat = NSMenuItem(
88-
title: "Open Chat",
89-
action: #selector(openGlobalChat),
90-
keyEquivalent: ""
91-
)
92-
93-
let quitItem = NSMenuItem(
94-
title: "Quit",
95-
action: #selector(quit),
96-
keyEquivalent: ""
97-
)
98-
quitItem.target = self
99-
100-
statusBarMenu.addItem(copilotName)
101-
statusBarMenu.addItem(openCopilotForXcode)
102-
statusBarMenu.addItem(checkForUpdate)
103-
statusBarMenu.addItem(.separator())
104-
statusBarMenu.addItem(openGlobalChat)
105-
statusBarMenu.addItem(.separator())
106-
statusBarMenu.addItem(quitItem)
107-
}
108-
10956
@objc func quit() {
11057
Task { @MainActor in
11158
await scheduledCleaner.closeAllChildProcesses()

Tool/Sources/Preferences/Keys.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,5 +361,9 @@ public extension UserDefaultPreferenceKeys {
361361
var animationCCrashSuggestion: FeatureFlag {
362362
.init(defaultValue: true, key: "FeatureFlag-AnimationCCrashSuggestion")
363363
}
364+
365+
var enableXcodeInspectorDebugMenu: FeatureFlag {
366+
.init(defaultValue: false, key: "FeatureFlag-EnableXcodeInspectorDebugMenu")
367+
}
364368
}
365369

0 commit comments

Comments
 (0)