Skip to content

Commit 81c3ba9

Browse files
committed
Merge branch 'feature/suggestion-extension' into develop
2 parents 948449d + 18b8344 commit 81c3ba9

File tree

47 files changed

+1032
-515
lines changed

Some content is hidden

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

47 files changed

+1032
-515
lines changed

Copilot for Xcode.xcodeproj/project.pbxproj

+15-43
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
C8216B782980370100AD38C7 /* ReloadLaunchAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8216B772980370100AD38C7 /* ReloadLaunchAgent.swift */; };
2424
C8216B7D2980374300AD38C7 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = C8216B7C2980374300AD38C7 /* ArgumentParser */; };
2525
C8216B802980378300AD38C7 /* Helper in Embed XPCService */ = {isa = PBXBuildFile; fileRef = C8216B70298036EC00AD38C7 /* Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
26+
C828B27F2B1F7B4F00E7612A /* ExtensionPoint.appextensionpoint in Copy Extension Point */ = {isa = PBXBuildFile; fileRef = C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */; };
2627
C8520301293C4D9000460097 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8520300293C4D9000460097 /* Helpers.swift */; };
2728
C861A6A329E5503F005C41A3 /* PromptToCodeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */; };
2829
C861E6112994F6070056CB02 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861E6102994F6070056CB02 /* AppDelegate.swift */; };
@@ -90,6 +91,17 @@
9091
);
9192
runOnlyForDeploymentPostprocessing = 1;
9293
};
94+
C828B27E2B1F7B3C00E7612A /* Copy Extension Point */ = {
95+
isa = PBXCopyFilesBuildPhase;
96+
buildActionMask = 2147483647;
97+
dstPath = "$(EXTENSIONS_FOLDER_PATH)";
98+
dstSubfolderSpec = 16;
99+
files = (
100+
C828B27F2B1F7B4F00E7612A /* ExtensionPoint.appextensionpoint in Copy Extension Point */,
101+
);
102+
name = "Copy Extension Point";
103+
runOnlyForDeploymentPostprocessing = 0;
104+
};
93105
C8520306293CF0EF00460097 /* Embed XPCService */ = {
94106
isa = PBXCopyFilesBuildPhase;
95107
buildActionMask = 2147483647;
@@ -163,6 +175,7 @@
163175
C8216B70298036EC00AD38C7 /* Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Helper; sourceTree = BUILT_PRODUCTS_DIR; };
164176
C8216B72298036EC00AD38C7 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
165177
C8216B772980370100AD38C7 /* ReloadLaunchAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadLaunchAgent.swift; sourceTree = "<group>"; };
178+
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = ExtensionPoint.appextensionpoint; sourceTree = "<group>"; };
166179
C82E38492A1F025F00D4EADF /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
167180
C83E5DED2A38CD8C0071506D /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
168181
C8520300293C4D9000460097 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
@@ -270,6 +283,7 @@
270283
C81458AD293A009600135263 /* Config.xcconfig */,
271284
C81458AE293A009800135263 /* Config.debug.xcconfig */,
272285
C8CD828229B88006008D044D /* TestPlan.xctestplan */,
286+
C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */,
273287
C81D181E2A1B509B006C1B70 /* Tool */,
274288
C8189B282938979000C9DCDA /* Core */,
275289
C8189B182938972F00C9DCDA /* Copilot for Xcode */,
@@ -413,8 +427,7 @@
413427
C861E60A2994F6070056CB02 /* Sources */,
414428
C861E60B2994F6070056CB02 /* Frameworks */,
415429
C861E60C2994F6070056CB02 /* Resources */,
416-
C8A3AE572A28852D0046E809 /* Sign Python STD */,
417-
C8A3B1782A2894E10046E809 /* Sign Python Site Packages */,
430+
C828B27E2B1F7B3C00E7612A /* Copy Extension Point */,
418431
);
419432
buildRules = (
420433
);
@@ -505,47 +518,6 @@
505518
};
506519
/* End PBXResourcesBuildPhase section */
507520

508-
/* Begin PBXShellScriptBuildPhase section */
509-
C8A3AE572A28852D0046E809 /* Sign Python STD */ = {
510-
isa = PBXShellScriptBuildPhase;
511-
alwaysOutOfDate = 1;
512-
buildActionMask = 8;
513-
files = (
514-
);
515-
inputFileListPaths = (
516-
);
517-
inputPaths = (
518-
);
519-
name = "Sign Python STD";
520-
outputFileListPaths = (
521-
);
522-
outputPaths = (
523-
);
524-
runOnlyForDeploymentPostprocessing = 1;
525-
shellPath = /bin/sh;
526-
shellScript = "#set -e\n#echo \"Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\n#find \"$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload\" -name \"*.so\" -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\;\n";
527-
};
528-
C8A3B1782A2894E10046E809 /* Sign Python Site Packages */ = {
529-
isa = PBXShellScriptBuildPhase;
530-
alwaysOutOfDate = 1;
531-
buildActionMask = 8;
532-
files = (
533-
);
534-
inputFileListPaths = (
535-
);
536-
inputPaths = (
537-
);
538-
name = "Sign Python Site Packages";
539-
outputFileListPaths = (
540-
);
541-
outputPaths = (
542-
);
543-
runOnlyForDeploymentPostprocessing = 1;
544-
shellPath = /bin/sh;
545-
shellScript = "#set -e\n#echo \"Signing as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)\"\n#find \"$CODESIGNING_FOLDER_PATH/Contents/Resources/site-packages\" -type f \\( -name \"*.so\" -o -name \"*.dylib\" \\) -exec /usr/bin/codesign --force --sign \"$EXPANDED_CODE_SIGN_IDENTITY\" -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der {} \\;\n";
546-
};
547-
/* End PBXShellScriptBuildPhase section */
548-
549521
/* Begin PBXSourcesBuildPhase section */
550522
C81458882939EFDC00135263 /* Sources */ = {
551523
isa = PBXSourcesBuildPhase;

Core/Package.swift

+16-5
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ let package = Package(
109109
name: "Client",
110110
dependencies: [
111111
.product(name: "XPCShared", package: "Tool"),
112-
.product(name: "SuggestionService", package: "Tool"),
112+
.product(name: "SuggestionProvider", package: "Tool"),
113113
.product(name: "SuggestionModel", package: "Tool"),
114114
.product(name: "Logger", package: "Tool"),
115115
.product(name: "Preferences", package: "Tool"),
@@ -119,12 +119,13 @@ let package = Package(
119119
name: "Service",
120120
dependencies: [
121121
"SuggestionWidget",
122+
"SuggestionService",
122123
"ChatService",
123124
"PromptToCodeService",
124125
"ServiceUpdateMigration",
125126
"ChatGPTChatTab",
126127
.product(name: "XPCShared", package: "Tool"),
127-
.product(name: "SuggestionService", package: "Tool"),
128+
.product(name: "SuggestionProvider", package: "Tool"),
128129
.product(name: "Workspace", package: "Tool"),
129130
.product(name: "UserDefaultsObserver", package: "Tool"),
130131
.product(name: "AppMonitoring", package: "Tool"),
@@ -149,7 +150,7 @@ let package = Package(
149150
"Client",
150151
"SuggestionInjector",
151152
.product(name: "XPCShared", package: "Tool"),
152-
.product(name: "SuggestionService", package: "Tool"),
153+
.product(name: "SuggestionProvider", package: "Tool"),
153154
.product(name: "SuggestionModel", package: "Tool"),
154155
.product(name: "Environment", package: "Tool"),
155156
.product(name: "Preferences", package: "Tool"),
@@ -164,7 +165,7 @@ let package = Package(
164165
"Client",
165166
"LaunchAgentManager",
166167
"PlusFeatureFlag",
167-
.product(name: "SuggestionService", package: "Tool"),
168+
.product(name: "SuggestionProvider", package: "Tool"),
168169
.product(name: "Toast", package: "Tool"),
169170
.product(name: "SharedUIComponents", package: "Tool"),
170171
.product(name: "SuggestionModel", package: "Tool"),
@@ -180,6 +181,15 @@ let package = Package(
180181

181182
// MARK: - Suggestion Service
182183

184+
.target(
185+
name: "SuggestionService",
186+
dependencies: [
187+
.product(name: "SuggestionModel", package: "Tool"),
188+
.product(name: "SuggestionProvider", package: "Tool")
189+
].pro([
190+
"ProExtension",
191+
])
192+
),
183193
.target(
184194
name: "SuggestionInjector",
185195
dependencies: [.product(name: "SuggestionModel", package: "Tool")]
@@ -263,6 +273,7 @@ let package = Package(
263273
dependencies: [
264274
"PromptToCodeService",
265275
"ChatGPTChatTab",
276+
.product(name: "Toast", package: "Tool"),
266277
.product(name: "UserDefaultsObserver", package: "Tool"),
267278
.product(name: "SharedUIComponents", package: "Tool"),
268279
.product(name: "AppMonitoring", package: "Tool"),
@@ -290,7 +301,7 @@ let package = Package(
290301
.target(
291302
name: "ServiceUpdateMigration",
292303
dependencies: [
293-
.product(name: "SuggestionService", package: "Tool"),
304+
.product(name: "SuggestionProvider", package: "Tool"),
294305
.product(name: "Preferences", package: "Tool"),
295306
.product(name: "Keychain", package: "Tool"),
296307
]

Core/Sources/Client/AsyncXPCService.swift

+33-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct AsyncXPCService {
2020
}
2121
}
2222
}
23-
23+
2424
public func getXPCServiceAccessibilityPermission() async throws -> Bool {
2525
try await withXPCServiceConnected(connection: connection) {
2626
service, continuation in
@@ -85,7 +85,7 @@ public struct AsyncXPCService {
8585
{ $0.getRealtimeSuggestedCode }
8686
)
8787
}
88-
88+
8989
public func getPromptToCodeAcceptedCode(editorContent: EditorContent) async throws
9090
-> UpdatedContent?
9191
{
@@ -144,7 +144,7 @@ public struct AsyncXPCService {
144144
{ service in { service.customCommand(id: id, editorContent: $0, withReply: $1) } }
145145
)
146146
}
147-
147+
148148
public func postNotification(name: String) async throws {
149149
try await withXPCServiceConnected(connection: connection) {
150150
service, continuation in
@@ -153,17 +153,41 @@ public struct AsyncXPCService {
153153
}
154154
}
155155
}
156-
157-
public func performAction(name: String, arguments: String) async throws -> String {
158-
try await withXPCServiceConnected(connection: connection) {
159-
service, continuation in
160-
service.performAction(name: name, arguments: arguments) {
161-
continuation.resume($0)
156+
157+
public func send<M: ExtensionServiceRequestType>(
158+
requestBody: M
159+
) async throws -> M.ResponseBody {
160+
try await withXPCServiceConnected(connection: connection) { service, continuation in
161+
do {
162+
let requestBodyData = try JSONEncoder().encode(requestBody)
163+
service.send(endpoint: M.endpoint, requestBody: requestBodyData) { data, error in
164+
if let error {
165+
continuation.reject(error)
166+
} else {
167+
do {
168+
guard let data = data else {
169+
continuation.reject(NoDataError())
170+
return
171+
}
172+
let responseBody = try JSONDecoder().decode(
173+
M.ResponseBody.self,
174+
from: data
175+
)
176+
continuation.resume(responseBody)
177+
} catch {
178+
continuation.reject(error)
179+
}
180+
}
181+
}
182+
} catch {
183+
continuation.reject(error)
162184
}
163185
}
164186
}
165187
}
166188

189+
struct NoDataError: Error {}
190+
167191
struct AutoFinishContinuation<T> {
168192
var continuation: AsyncThrowingStream<T, Error>.Continuation
169193

Core/Sources/HostApp/FeatureSettings/SuggestionSettingsView.swift

+107-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
1+
import Client
12
import Preferences
23
import SharedUIComponents
34
import SwiftUI
5+
import XPCShared
46

57
#if canImport(ProHostApp)
68
import ProHostApp
79
#endif
810

911
struct SuggestionSettingsView: View {
12+
struct SuggestionFeatureProviderOption: Identifiable, Hashable {
13+
var id: String {
14+
(builtInProvider?.rawValue).map(String.init) ?? bundleIdentifier ?? "n/A"
15+
}
16+
17+
var name: String
18+
var builtInProvider: BuiltInSuggestionFeatureProvider?
19+
var bundleIdentifier: String?
20+
21+
func hash(into hasher: inout Hasher) {
22+
id.hash(into: &hasher)
23+
}
24+
25+
init(
26+
name: String,
27+
builtInProvider: BuiltInSuggestionFeatureProvider? = nil,
28+
bundleIdentifier: String? = nil
29+
) {
30+
self.name = name
31+
self.builtInProvider = builtInProvider
32+
self.bundleIdentifier = bundleIdentifier
33+
}
34+
}
35+
1036
final class Settings: ObservableObject {
1137
@AppStorage(\.realtimeSuggestionToggle)
1238
var realtimeSuggestionToggle
@@ -30,7 +56,42 @@ struct SuggestionSettingsView: View {
3056
var acceptSuggestionWithTab
3157
@AppStorage(\.isSuggestionSenseEnabled)
3258
var isSuggestionSenseEnabled
33-
init() {}
59+
60+
var refreshExtensionSuggestionFeatureProvidersTask: Task<Void, Never>?
61+
62+
@MainActor
63+
@Published
64+
var extensionSuggestionFeatureProviderOptions = [SuggestionFeatureProviderOption]()
65+
66+
init() {
67+
Task { @MainActor in
68+
refreshExtensionSuggestionFeatureProviders()
69+
}
70+
refreshExtensionSuggestionFeatureProvidersTask = Task { [weak self] in
71+
let sequence = await NotificationCenter.default
72+
.notifications(named: NSApplication.didBecomeActiveNotification)
73+
for await _ in sequence {
74+
guard let self else { return }
75+
await MainActor.run {
76+
self.refreshExtensionSuggestionFeatureProviders()
77+
}
78+
}
79+
}
80+
}
81+
82+
@MainActor
83+
func refreshExtensionSuggestionFeatureProviders() {
84+
guard let service = try? getService() else { return }
85+
Task { @MainActor in
86+
let services = try await service
87+
.send(requestBody: ExtensionServiceRequests.GetExtensionSuggestionServices())
88+
extensionSuggestionFeatureProviderOptions = services.map {
89+
.init(name: $0.name, bundleIdentifier: $0.bundleIdentifier)
90+
}
91+
print(services.map(\.bundleIdentifier))
92+
print(suggestionFeatureProvider)
93+
}
94+
}
3495
}
3596

3697
@StateObject var settings = Settings()
@@ -52,13 +113,54 @@ struct SuggestionSettingsView: View {
52113
Text("Presentation")
53114
}
54115

55-
Picker(selection: $settings.suggestionFeatureProvider) {
56-
ForEach(SuggestionFeatureProvider.allCases, id: \.rawValue) {
116+
Picker(selection: Binding(get: {
117+
switch settings.suggestionFeatureProvider {
118+
case let .builtIn(provider):
119+
return SuggestionFeatureProviderOption(
120+
name: "",
121+
builtInProvider: provider
122+
)
123+
case let .extension(name, identifier):
124+
return SuggestionFeatureProviderOption(
125+
name: name,
126+
bundleIdentifier: identifier
127+
)
128+
}
129+
}, set: { (option: SuggestionFeatureProviderOption) in
130+
if let provider = option.builtInProvider {
131+
settings.suggestionFeatureProvider = .builtIn(provider)
132+
} else {
133+
settings.suggestionFeatureProvider = .extension(
134+
name: option.name,
135+
bundleIdentifier: option.bundleIdentifier ?? ""
136+
)
137+
}
138+
})) {
139+
ForEach(BuiltInSuggestionFeatureProvider.allCases, id: \.rawValue) {
57140
switch $0 {
58141
case .gitHubCopilot:
59-
Text("GitHub Copilot").tag($0)
142+
Text("GitHub Copilot")
143+
.tag(SuggestionFeatureProviderOption(name: "", builtInProvider: $0))
60144
case .codeium:
61-
Text("Codeium").tag($0)
145+
Text("Codeium")
146+
.tag(SuggestionFeatureProviderOption(name: "", builtInProvider: $0))
147+
}
148+
}
149+
150+
ForEach(settings.extensionSuggestionFeatureProviderOptions, id: \.self) { item in
151+
Text(item.name).tag(item)
152+
}
153+
154+
if case let .extension(name, identifier) = settings.suggestionFeatureProvider {
155+
if !settings.extensionSuggestionFeatureProviderOptions.contains(where: {
156+
$0.bundleIdentifier == identifier
157+
}) {
158+
Text("\(name) (Not Found)").tag(
159+
SuggestionFeatureProviderOption(
160+
name: name,
161+
bundleIdentifier: identifier
162+
)
163+
)
62164
}
63165
}
64166
} label: {

0 commit comments

Comments
 (0)