Skip to content

Commit 8a42e7a

Browse files
committed
Add new target BuiltinExtension
1 parent 8261fac commit 8a42e7a

6 files changed

Lines changed: 336 additions & 2 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1530"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
<BuildActionEntries>
10+
<BuildActionEntry
11+
buildForTesting = "YES"
12+
buildForRunning = "YES"
13+
buildForProfiling = "YES"
14+
buildForArchiving = "YES"
15+
buildForAnalyzing = "YES">
16+
<BuildableReference
17+
BuildableIdentifier = "primary"
18+
BlueprintIdentifier = "SuggestionModel"
19+
BuildableName = "SuggestionModel"
20+
BlueprintName = "SuggestionModel"
21+
ReferencedContainer = "container:">
22+
</BuildableReference>
23+
</BuildActionEntry>
24+
</BuildActionEntries>
25+
</BuildAction>
26+
<TestAction
27+
buildConfiguration = "Debug"
28+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
29+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
30+
shouldUseLaunchSchemeArgsEnv = "YES"
31+
shouldAutocreateTestPlan = "YES">
32+
</TestAction>
33+
<LaunchAction
34+
buildConfiguration = "Debug"
35+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
36+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
37+
launchStyle = "0"
38+
useCustomWorkingDirectory = "NO"
39+
ignoresPersistentStateOnLaunch = "NO"
40+
debugDocumentVersioning = "YES"
41+
debugServiceExtension = "internal"
42+
allowLocationSimulation = "YES">
43+
</LaunchAction>
44+
<ProfileAction
45+
buildConfiguration = "Release"
46+
shouldUseLaunchSchemeArgsEnv = "YES"
47+
savedToolIdentifier = ""
48+
useCustomWorkingDirectory = "NO"
49+
debugDocumentVersioning = "YES">
50+
<MacroExpansion>
51+
<BuildableReference
52+
BuildableIdentifier = "primary"
53+
BlueprintIdentifier = "SuggestionModel"
54+
BuildableName = "SuggestionModel"
55+
BlueprintName = "SuggestionModel"
56+
ReferencedContainer = "container:">
57+
</BuildableReference>
58+
</MacroExpansion>
59+
</ProfileAction>
60+
<AnalyzeAction
61+
buildConfiguration = "Debug">
62+
</AnalyzeAction>
63+
<ArchiveAction
64+
buildConfiguration = "Release"
65+
revealArchiveInOrganizer = "YES">
66+
</ArchiveAction>
67+
</Scheme>

Tool/Package.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ let package = Package(
202202

203203
.target(name: "AsyncPassthroughSubject"),
204204

205+
.target(
206+
name: "BuiltinExtension",
207+
dependencies: [
208+
"SuggestionModel",
209+
"Workspace",
210+
.product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit")
211+
]
212+
),
213+
205214
.target(
206215
name: "SharedUIComponents",
207216
dependencies: [
@@ -239,6 +248,7 @@ let package = Package(
239248
"Workspace",
240249
"SuggestionProvider",
241250
"XPCShared",
251+
"BuiltinExtension"
242252
]
243253
),
244254

@@ -285,9 +295,8 @@ let package = Package(
285295
.target(name: "BingSearchService"),
286296

287297
.target(name: "SuggestionProvider", dependencies: [
288-
"GitHubCopilotService",
289-
"CodeiumService",
290298
"UserDefaultsObserver",
299+
"Preferences",
291300
.product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit"),
292301
]),
293302

@@ -303,7 +312,9 @@ let package = Package(
303312
"Logger",
304313
"Preferences",
305314
"Terminal",
315+
"BuiltinExtension",
306316
.product(name: "LanguageServerProtocol", package: "LanguageServerProtocol"),
317+
.product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit"),
307318
]
308319
),
309320
.testTarget(
@@ -322,6 +333,8 @@ let package = Package(
322333
"Preferences",
323334
"Terminal",
324335
"XcodeInspector",
336+
"BuiltinExtension",
337+
.product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit"),
325338
]
326339
),
327340

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import CopilotForXcodeKit
2+
import Foundation
3+
4+
public protocol BuiltinExtension: CopilotForXcodeExtensionCapability {
5+
func terminate()
6+
}
7+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Foundation
2+
3+
public final class BuiltinExtensionManager {
4+
public static let shared: BuiltinExtensionManager = .init()
5+
private(set) var extensions: [BuiltinExtension] = []
6+
7+
public func setupExtensions(_ extensions: [BuiltinExtension]) {
8+
self.extensions = extensions
9+
}
10+
11+
public func terminate() {
12+
for ext in extensions {
13+
ext.terminate()
14+
}
15+
}
16+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import CopilotForXcodeKit
2+
import Foundation
3+
import Preferences
4+
import SuggestionModel
5+
import SuggestionProvider
6+
7+
public final class BuiltinExtensionSuggestionServiceProvider<
8+
T: BuiltinExtension
9+
>: SuggestionServiceProvider {
10+
public var configuration: SuggestionServiceConfiguration {
11+
guard let service else {
12+
return .init(
13+
acceptsRelevantCodeSnippets: true,
14+
mixRelevantCodeSnippetsInSource: true,
15+
acceptsRelevantSnippetsFromOpenedFiles: true
16+
)
17+
}
18+
19+
return service.configuration
20+
}
21+
22+
let extensionManager: BuiltinExtensionManager
23+
24+
public init(
25+
extension: T.Type,
26+
extensionManager: BuiltinExtensionManager = .shared
27+
) {
28+
self.extensionManager = extensionManager
29+
}
30+
31+
var service: CopilotForXcodeKit.SuggestionServiceType? {
32+
extensionManager.extensions.first { $0 is T }?.suggestionService
33+
}
34+
35+
public func getSuggestions(
36+
_ request: SuggestionProvider.SuggestionRequest,
37+
workspaceInfo: CopilotForXcodeKit.WorkspaceInfo
38+
) async throws -> [SuggestionModel.CodeSuggestion] {
39+
guard let service else { return [] }
40+
return try await service.getSuggestions(
41+
.init(
42+
fileURL: request.fileURL,
43+
relativePath: request.relativePath,
44+
language: .init(
45+
rawValue: languageIdentifierFromFileURL(request.fileURL).rawValue
46+
) ?? .plaintext,
47+
content: request.content,
48+
cursorPosition: .init(
49+
line: request.cursorPosition.line,
50+
character: request.cursorPosition.character
51+
),
52+
tabSize: request.tabSize,
53+
indentSize: request.indentSize,
54+
usesTabsForIndentation: request.usesTabsForIndentation,
55+
relevantCodeSnippets: request.relevantCodeSnippets.map { $0.converted }
56+
),
57+
workspace: workspaceInfo
58+
).map { $0.converted }
59+
}
60+
61+
public func cancelRequest(
62+
workspaceInfo: CopilotForXcodeKit.WorkspaceInfo
63+
) async {
64+
guard let service else { return }
65+
await service.cancelRequest(workspace: workspaceInfo)
66+
}
67+
68+
public func notifyAccepted(
69+
_ suggestion: SuggestionModel.CodeSuggestion,
70+
workspaceInfo: CopilotForXcodeKit.WorkspaceInfo
71+
) async {
72+
guard let service else { return }
73+
await service.notifyAccepted(suggestion.converted, workspace: workspaceInfo)
74+
}
75+
76+
public func notifyRejected(
77+
_ suggestions: [SuggestionModel.CodeSuggestion],
78+
workspaceInfo: CopilotForXcodeKit.WorkspaceInfo
79+
) async {
80+
guard let service else { return }
81+
await service.notifyRejected(suggestions.map(\.converted), workspace: workspaceInfo)
82+
}
83+
}
84+
85+
extension SuggestionProvider.SuggestionRequest {
86+
var converted: CopilotForXcodeKit.SuggestionRequest {
87+
.init(
88+
fileURL: fileURL,
89+
relativePath: relativePath,
90+
language: .init(rawValue: languageIdentifierFromFileURL(fileURL).rawValue)
91+
?? .plaintext,
92+
content: content,
93+
cursorPosition: .init(
94+
line: cursorPosition.line,
95+
character: cursorPosition.character
96+
),
97+
tabSize: tabSize,
98+
indentSize: indentSize,
99+
usesTabsForIndentation: usesTabsForIndentation,
100+
relevantCodeSnippets: relevantCodeSnippets.map(\.converted)
101+
)
102+
}
103+
}
104+
105+
extension SuggestionModel.CodeSuggestion {
106+
var converted: CopilotForXcodeKit.CodeSuggestion {
107+
.init(
108+
id: id,
109+
text: text,
110+
position: .init(
111+
line: position.line,
112+
character: position.character
113+
),
114+
range: .init(
115+
start: .init(
116+
line: range.start.line,
117+
character: range.start.character
118+
),
119+
end: .init(
120+
line: range.end.line,
121+
character: range.end.character
122+
)
123+
)
124+
)
125+
}
126+
}
127+
128+
extension CopilotForXcodeKit.CodeSuggestion {
129+
var converted: SuggestionModel.CodeSuggestion {
130+
.init(
131+
id: id,
132+
text: text,
133+
position: .init(
134+
line: position.line,
135+
character: position.character
136+
),
137+
range: .init(
138+
start: .init(
139+
line: range.start.line,
140+
character: range.start.character
141+
),
142+
end: .init(
143+
line: range.end.line,
144+
character: range.end.character
145+
)
146+
)
147+
)
148+
}
149+
}
150+
151+
extension SuggestionProvider.RelevantCodeSnippet {
152+
var converted: CopilotForXcodeKit.RelevantCodeSnippet {
153+
.init(content: content, priority: priority, filePath: filePath)
154+
}
155+
}
156+
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
import Workspace
3+
4+
final class BuiltinExtensionWorkspacePlugin: WorkspacePlugin {
5+
let extensionManager: BuiltinExtensionManager
6+
7+
public init(workspace: Workspace, extensionManager: BuiltinExtensionManager) {
8+
self.extensionManager = extensionManager
9+
super.init(workspace: workspace)
10+
}
11+
12+
override public func didOpenFilespace(_ filespace: Filespace) {
13+
notifyOpenFile(filespace: filespace)
14+
}
15+
16+
override public func didSaveFilespace(_ filespace: Filespace) {
17+
notifySaveFile(filespace: filespace)
18+
}
19+
20+
override public func didUpdateFilespace(_ filespace: Filespace, content: String) {
21+
notifyUpdateFile(filespace: filespace, content: content)
22+
}
23+
24+
override public func didCloseFilespace(_ fileURL: URL) {
25+
Task {
26+
for ext in extensionManager.extensions {
27+
ext.workspace(
28+
.init(workspaceURL: workspaceURL, projectURL: projectRootURL),
29+
didCloseDocumentAt: fileURL
30+
)
31+
}
32+
}
33+
}
34+
35+
public func notifyOpenFile(filespace: Filespace) {
36+
Task {
37+
guard filespace.isTextReadable else { return }
38+
guard !(await filespace.isGitIgnored) else { return }
39+
for ext in extensionManager.extensions {
40+
ext.workspace(
41+
.init(workspaceURL: workspaceURL, projectURL: projectRootURL),
42+
didOpenDocumentAt: filespace.fileURL
43+
)
44+
}
45+
}
46+
}
47+
48+
public func notifyUpdateFile(filespace: Filespace, content: String) {
49+
Task {
50+
guard filespace.isTextReadable else { return }
51+
guard !(await filespace.isGitIgnored) else { return }
52+
for ext in extensionManager.extensions {
53+
ext.workspace(
54+
.init(workspaceURL: workspaceURL, projectURL: projectRootURL),
55+
didUpdateDocumentAt: filespace.fileURL,
56+
content: content
57+
)
58+
}
59+
}
60+
}
61+
62+
public func notifySaveFile(filespace: Filespace) {
63+
Task {
64+
guard filespace.isTextReadable else { return }
65+
guard !(await filespace.isGitIgnored) else { return }
66+
for ext in extensionManager.extensions {
67+
ext.workspace(
68+
.init(workspaceURL: workspaceURL, projectURL: projectRootURL),
69+
didSaveDocumentAt: filespace.fileURL
70+
)
71+
}
72+
}
73+
}
74+
}
75+

0 commit comments

Comments
 (0)