Skip to content

Commit 3f04b30

Browse files
committed
Merge branch 'release/0.8.0'
2 parents c1fbe42 + 53f3290 commit 3f04b30

45 files changed

Lines changed: 2792 additions & 1039 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
C87F3E62293DD004008523E8 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87F3E61293DD004008523E8 /* Styles.swift */; };
4646
C882175A294187E100A22FD3 /* Client in Frameworks */ = {isa = PBXBuildFile; productRef = C8821759294187E100A22FD3 /* Client */; };
4747
C882175C294187EF00A22FD3 /* Client in Frameworks */ = {isa = PBXBuildFile; productRef = C882175B294187EF00A22FD3 /* Client */; };
48-
C8E93DB429950C8A00E6D43D /* CopilotForXcodeExtensionService.app in Embed XPCService */ = {isa = PBXBuildFile; fileRef = C861E60E2994F6070056CB02 /* CopilotForXcodeExtensionService.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
48+
C8C8B60929AFA35F00034BEE /* CopilotForXcodeExtensionService.app in Embed Service */ = {isa = PBXBuildFile; fileRef = C861E60E2994F6070056CB02 /* CopilotForXcodeExtensionService.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4949
/* End PBXBuildFile section */
5050

5151
/* Begin PBXContainerItemProxy section */
@@ -110,31 +110,31 @@
110110
dstPath = ../Applications;
111111
dstSubfolderSpec = 6;
112112
files = (
113-
C8E93DB429950C8A00E6D43D /* CopilotForXcodeExtensionService.app in Embed XPCService */,
114113
C8216B802980378300AD38C7 /* Helper in Embed XPCService */,
115114
);
116115
name = "Embed XPCService";
117116
runOnlyForDeploymentPostprocessing = 0;
118117
};
119-
C861E6062994F50D0056CB02 /* Embed Old XPCService Target */ = {
118+
C87B03AE293B2CF300C77EAE /* Embed Frameworks */ = {
120119
isa = PBXCopyFilesBuildPhase;
121120
buildActionMask = 2147483647;
122121
dstPath = "";
123-
dstSubfolderSpec = 6;
122+
dstSubfolderSpec = 10;
124123
files = (
124+
C87B03AD293B2CF300C77EAE /* XcodeKit.framework in Embed Frameworks */,
125125
);
126-
name = "Embed Old XPCService Target";
126+
name = "Embed Frameworks";
127127
runOnlyForDeploymentPostprocessing = 0;
128128
};
129-
C87B03AE293B2CF300C77EAE /* Embed Frameworks */ = {
129+
C8C8B60829AFA32800034BEE /* Embed Service */ = {
130130
isa = PBXCopyFilesBuildPhase;
131131
buildActionMask = 2147483647;
132-
dstPath = "";
133-
dstSubfolderSpec = 10;
132+
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
133+
dstSubfolderSpec = 16;
134134
files = (
135-
C87B03AD293B2CF300C77EAE /* XcodeKit.framework in Embed Frameworks */,
135+
C8C8B60929AFA35F00034BEE /* CopilotForXcodeExtensionService.app in Embed Service */,
136136
);
137-
name = "Embed Frameworks";
137+
name = "Embed Service";
138138
runOnlyForDeploymentPostprocessing = 0;
139139
};
140140
/* End PBXCopyFilesBuildPhase section */
@@ -369,7 +369,7 @@
369369
C8189B142938972F00C9DCDA /* Resources */,
370370
C814589F2939EFDC00135263 /* Embed Foundation Extensions */,
371371
C8520306293CF0EF00460097 /* Embed XPCService */,
372-
C861E6062994F50D0056CB02 /* Embed Old XPCService Target */,
372+
C8C8B60829AFA32800034BEE /* Embed Service */,
373373
);
374374
buildRules = (
375375
);

Copilot for Xcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Copilot for Xcode/App.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import SwiftUI
2+
import XPCShared
23

34
@main
45
struct CopilotForXcodeApp: App {
@@ -7,6 +8,9 @@ struct CopilotForXcodeApp: App {
78
ContentView()
89
.frame(minWidth: 500, minHeight: 700)
910
.preferredColorScheme(.dark)
11+
.onAppear {
12+
UserDefaults.setupDefaultSettings()
13+
}
1014
}
1115
.windowStyle(.hiddenTitleBar)
1216
}

Copilot for Xcode/LaunchAgentManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ extension LaunchAgentManager {
99
".ExtensionService",
1010
executablePath: Bundle.main.bundleURL
1111
.appendingPathComponent("Contents")
12-
.appendingPathComponent("Applications")
12+
.appendingPathComponent("XPCServices")
1313
.appendingPathComponent(
1414
"CopilotForXcodeExtensionService.app/Contents/MacOS/CopilotForXcodeExtensionService"
1515
)

Copilot for Xcode/LaunchAgentView.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,23 @@ struct LaunchAgentView: View {
9090
TextField("node", text: $nodePath)
9191
.textFieldStyle(.copilot)
9292
}
93+
94+
HStack {
95+
Button(action: {
96+
Task {
97+
let workspace = NSWorkspace.shared
98+
let url = Bundle.main.bundleURL
99+
.appendingPathComponent("Contents")
100+
.appendingPathComponent("XPCServices")
101+
.appendingPathComponent("CopilotForXcodeExtensionService.app")
102+
workspace.activateFileViewerSelecting([url])
103+
}
104+
}) {
105+
Text("Reveal Extension App in Finder")
106+
}
107+
108+
Spacer()
109+
}
93110
}
94111
}
95112
.buttonStyle(.copilot)

Copilot for Xcode/SettingsView.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,45 @@ import LaunchAgentManager
22
import SwiftUI
33
import XPCShared
44

5-
struct SettingsView: View {
5+
final class Settings: ObservableObject {
66
@AppStorage(SettingsKey.quitXPCServiceOnXcodeAndAppQuit, store: .shared)
77
var quitXPCServiceOnXcodeAndAppQuit: Bool = false
88
@AppStorage(SettingsKey.realtimeSuggestionToggle, store: .shared)
99
var realtimeSuggestionToggle: Bool = false
1010
@AppStorage(SettingsKey.realtimeSuggestionDebounce, store: .shared)
1111
var realtimeSuggestionDebounce: Double = 0.7
12+
@AppStorage(SettingsKey.suggestionPresentationMode, store: .shared)
13+
var suggestionPresentationModeRawValue: Int = 0
14+
init() {}
15+
}
16+
17+
struct SettingsView: View {
18+
@StateObject var settings = Settings()
1219
@State var editingRealtimeSuggestionDebounce: Double = UserDefaults.shared
1320
.value(forKey: SettingsKey.realtimeSuggestionDebounce) as? Double ?? 0.7
1421

1522
var body: some View {
1623
Section {
1724
Form {
18-
Toggle(isOn: $quitXPCServiceOnXcodeAndAppQuit) {
25+
Toggle(isOn: $settings.quitXPCServiceOnXcodeAndAppQuit) {
1926
Text("Quit service when Xcode and host app are terminated")
2027
}
2128
.toggleStyle(.switch)
22-
Toggle(isOn: $realtimeSuggestionToggle) {
29+
30+
Picker(selection: $settings.suggestionPresentationModeRawValue) {
31+
ForEach(PresentationMode.allCases, id: \.rawValue) {
32+
switch $0 {
33+
case .comment:
34+
Text("Comment")
35+
case .floatingWidget:
36+
Text("Floating Widget")
37+
}
38+
}
39+
} label: {
40+
Text("Present suggestions in")
41+
}
42+
43+
Toggle(isOn: $settings.realtimeSuggestionToggle) {
2344
Text("Real-time suggestion")
2445
}
2546
.toggleStyle(.switch)
@@ -28,7 +49,7 @@ struct SettingsView: View {
2849
Slider(value: $editingRealtimeSuggestionDebounce, in: 0...2, step: 0.1) {
2950
Text("Real-time suggestion fetch debounce")
3051
} onEditingChanged: { _ in
31-
realtimeSuggestionDebounce = editingRealtimeSuggestionDebounce
52+
settings.realtimeSuggestionDebounce = editingRealtimeSuggestionDebounce
3253
}
3354

3455
Text(

Core/Package.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let package = Package(
1818
],
1919
dependencies: [
2020
.package(url: "https://github.com/ChimeHQ/LanguageClient", from: "0.3.1"),
21+
.package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"),
2122
],
2223
targets: [
2324
.target(name: "CGEventObserver"),
@@ -51,17 +52,50 @@ let package = Package(
5152
),
5253
.target(
5354
name: "Service",
54-
dependencies: ["CopilotModel", "CopilotService", "XPCShared", "CGEventObserver"]
55+
dependencies: [
56+
"CopilotModel",
57+
"CopilotService",
58+
"XPCShared",
59+
"CGEventObserver",
60+
"DisplayLink",
61+
"ActiveApplicationMonitor",
62+
"AXNotificationStream",
63+
"Environment",
64+
"SuggestionWidget",
65+
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
66+
]
5567
),
5668
.target(
5769
name: "XPCShared",
5870
dependencies: ["CopilotModel"]
5971
),
6072
.testTarget(
6173
name: "ServiceTests",
62-
dependencies: ["Service", "Client", "CopilotService", "SuggestionInjector", "XPCShared"]
74+
dependencies: [
75+
"Service",
76+
"Client",
77+
"CopilotService",
78+
"SuggestionInjector",
79+
"XPCShared",
80+
"Environment",
81+
]
6382
),
6483
.target(name: "FileChangeChecker"),
6584
.target(name: "LaunchAgentManager"),
85+
.target(name: "DisplayLink"),
86+
.target(name: "ActiveApplicationMonitor"),
87+
.target(name: "AXNotificationStream"),
88+
.target(
89+
name: "Environment",
90+
dependencies: ["ActiveApplicationMonitor", "CopilotService"]
91+
),
92+
.target(
93+
name: "SuggestionWidget",
94+
dependencies: [
95+
"ActiveApplicationMonitor",
96+
"AXNotificationStream",
97+
"Environment",
98+
]
99+
),
66100
]
67101
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import AppKit
2+
import ApplicationServices
3+
import Foundation
4+
5+
public final class AXNotificationStream: AsyncSequence {
6+
public typealias Stream = AsyncStream<Element>
7+
public typealias Continuation = Stream.Continuation
8+
public typealias AsyncIterator = Stream.AsyncIterator
9+
public typealias Element = (name: String, info: CFDictionary)
10+
11+
private var continuation: Continuation
12+
private let stream: Stream
13+
14+
public func makeAsyncIterator() -> Stream.AsyncIterator {
15+
stream.makeAsyncIterator()
16+
}
17+
18+
deinit {
19+
continuation.finish()
20+
}
21+
22+
public init(
23+
app: NSRunningApplication,
24+
element: AXUIElement? = nil,
25+
notificationNames: String...
26+
) {
27+
var cont: Continuation!
28+
stream = Stream { continuation in
29+
cont = continuation
30+
}
31+
continuation = cont
32+
var observer: AXObserver?
33+
34+
func callback(
35+
observer: AXObserver,
36+
element: AXUIElement,
37+
notificationName: CFString,
38+
userInfo: CFDictionary,
39+
pointer: UnsafeMutableRawPointer?
40+
) {
41+
guard let pointer = pointer?.assumingMemoryBound(to: Continuation.self)
42+
else { return }
43+
pointer.pointee.yield((notificationName as String, userInfo))
44+
}
45+
46+
_ = AXObserverCreateWithInfoCallback(
47+
app.processIdentifier,
48+
callback,
49+
&observer
50+
)
51+
guard let observer else {
52+
continuation.finish()
53+
return
54+
}
55+
56+
let observingElement = element ?? AXUIElementCreateApplication(app.processIdentifier)
57+
continuation.onTermination = { @Sendable _ in
58+
for name in notificationNames {
59+
AXObserverRemoveNotification(observer, observingElement, name as CFString)
60+
}
61+
CFRunLoopRemoveSource(
62+
CFRunLoopGetMain(),
63+
AXObserverGetRunLoopSource(observer),
64+
.commonModes
65+
)
66+
}
67+
for name in notificationNames {
68+
AXObserverAddNotification(observer, observingElement, name as CFString, &continuation)
69+
}
70+
CFRunLoopAddSource(
71+
CFRunLoopGetMain(),
72+
AXObserverGetRunLoopSource(observer),
73+
.commonModes
74+
)
75+
}
76+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import AppKit
2+
3+
public final class ActiveApplicationMonitor {
4+
static let shared = ActiveApplicationMonitor()
5+
var activeApplication = NSWorkspace.shared.runningApplications.first(where: \.isActive)
6+
private var continuations: [UUID: AsyncStream<NSRunningApplication?>.Continuation] = [:]
7+
8+
private init() {
9+
Task {
10+
let sequence = NSWorkspace.shared.notificationCenter
11+
.notifications(named: NSWorkspace.didActivateApplicationNotification)
12+
for await notification in sequence {
13+
guard let app = notification
14+
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication
15+
else { continue }
16+
activeApplication = app
17+
notifyContinuations()
18+
}
19+
}
20+
}
21+
22+
deinit {
23+
for continuation in continuations {
24+
continuation.value.finish()
25+
}
26+
}
27+
28+
public static var activeApplication: NSRunningApplication? { shared.activeApplication }
29+
30+
public static var activeXcode: NSRunningApplication? {
31+
if activeApplication?.bundleIdentifier == "com.apple.dt.Xcode" {
32+
return activeApplication
33+
}
34+
return nil
35+
}
36+
37+
public static func createStream() -> AsyncStream<NSRunningApplication?> {
38+
.init { continuation in
39+
let id = UUID()
40+
ActiveApplicationMonitor.shared.addContinuation(continuation, id: id)
41+
continuation.onTermination = { _ in
42+
ActiveApplicationMonitor.shared.removeContinuation(id: id)
43+
}
44+
continuation.yield(activeApplication)
45+
}
46+
}
47+
48+
func addContinuation(
49+
_ continuation: AsyncStream<NSRunningApplication?>.Continuation,
50+
id: UUID
51+
) {
52+
continuations[id] = continuation
53+
}
54+
55+
func removeContinuation(id: UUID) {
56+
continuations[id] = nil
57+
}
58+
59+
private func notifyContinuations() {
60+
for continuation in continuations {
61+
continuation.value.yield(activeApplication)
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)