Skip to content

Commit 496acee

Browse files
committed
Merge tag '0.3.0' into develop
2 parents eec1adf + c3cfecd commit 496acee

25 files changed

Lines changed: 208 additions & 101 deletions

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@
695695
CODE_SIGN_ENTITLEMENTS = "Copilot for Xcode/Copilot_for_Xcode.entitlements";
696696
CODE_SIGN_STYLE = Automatic;
697697
COMBINE_HIDPI_IMAGES = YES;
698-
CURRENT_PROJECT_VERSION = 4;
698+
CURRENT_PROJECT_VERSION = 9;
699699
DEVELOPMENT_ASSET_PATHS = "\"Copilot for Xcode/Preview Content\"";
700700
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
701701
ENABLE_HARDENED_RUNTIME = YES;
@@ -709,7 +709,7 @@
709709
"@executable_path/../Frameworks",
710710
);
711711
MACOSX_DEPLOYMENT_TARGET = 12.0;
712-
MARKETING_VERSION = 0.2.0;
712+
MARKETING_VERSION = 0.3.0;
713713
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE)";
714714
PRODUCT_MODULE_NAME = Copilot_for_Xcode;
715715
PRODUCT_NAME = "Copilot for Xcode Dev";
@@ -727,7 +727,7 @@
727727
CODE_SIGN_ENTITLEMENTS = "Copilot for Xcode/Copilot_for_Xcode.entitlements";
728728
CODE_SIGN_STYLE = Automatic;
729729
COMBINE_HIDPI_IMAGES = YES;
730-
CURRENT_PROJECT_VERSION = 4;
730+
CURRENT_PROJECT_VERSION = 9;
731731
DEVELOPMENT_ASSET_PATHS = "\"Copilot for Xcode/Preview Content\"";
732732
DEVELOPMENT_TEAM = 5YKZ4Y3DAW;
733733
ENABLE_HARDENED_RUNTIME = YES;
@@ -741,7 +741,7 @@
741741
"@executable_path/../Frameworks",
742742
);
743743
MACOSX_DEPLOYMENT_TARGET = 12.0;
744-
MARKETING_VERSION = 0.2.0;
744+
MARKETING_VERSION = 0.3.0;
745745
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE)";
746746
PRODUCT_NAME = "Copilot for Xcode";
747747
SWIFT_EMIT_LOC_STRINGS = YES;

Copilot for Xcode/InstructionView.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@ Text("""
2121
7. Restart Xcode, the Copilot commands should be available in the menu bar.
2222
""")
2323

24-
Text("Permissions Requirement")
25-
.font(.title3)
26-
27-
Text("""
28-
- The extension will ask for Accessibility API permission the first time it runs.
29-
- The extension may ask for folder access permission when it runs.
30-
""")
31-
3224
Text("Disable Extension")
3325
.font(.title3)
3426

Core/Package.resolved

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

Core/Sources/Client/AsyncXPCService.swift

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,47 +80,47 @@ public struct AsyncXPCService {
8080
}
8181
}
8282

83-
public func getSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent {
83+
public func getSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
8484
try await suggestionRequest(
8585
connection,
8686
editorContent,
8787
{ $0.getSuggestedCode }
8888
)
8989
}
9090

91-
public func getNextSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent {
91+
public func getNextSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
9292
try await suggestionRequest(
9393
connection,
9494
editorContent,
9595
{ $0.getNextSuggestedCode }
9696
)
9797
}
9898

99-
public func getPreviousSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent {
99+
public func getPreviousSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
100100
try await suggestionRequest(
101101
connection,
102102
editorContent,
103103
{ $0.getPreviousSuggestedCode }
104104
)
105105
}
106106

107-
public func getSuggestionAcceptedCode(editorContent: EditorContent) async throws -> UpdatedContent {
107+
public func getSuggestionAcceptedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
108108
try await suggestionRequest(
109109
connection,
110110
editorContent,
111111
{ $0.getSuggestionAcceptedCode }
112112
)
113113
}
114114

115-
public func getSuggestionRejectedCode(editorContent: EditorContent) async throws -> UpdatedContent {
115+
public func getSuggestionRejectedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
116116
try await suggestionRequest(
117117
connection,
118118
editorContent,
119119
{ $0.getSuggestionRejectedCode }
120120
)
121121
}
122122

123-
public func getRealtimeSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent {
123+
public func getRealtimeSuggestedCode(editorContent: EditorContent) async throws -> UpdatedContent? {
124124
try await suggestionRequest(
125125
connection,
126126
editorContent,
@@ -172,7 +172,7 @@ func suggestionRequest(
172172
_ connection: NSXPCConnection,
173173
_ editorContent: EditorContent,
174174
_ fn: @escaping (any XPCServiceProtocol) -> (Data, @escaping (Data?, Error?) -> Void) -> Void
175-
) async throws -> UpdatedContent {
175+
) async throws -> UpdatedContent? {
176176
let data = try JSONEncoder().encode(editorContent)
177177
return try await withXPCServiceConnected(connection: connection) {
178178
service, continuation in
@@ -182,9 +182,13 @@ func suggestionRequest(
182182
return
183183
}
184184
do {
185-
let updatedContent = try JSONDecoder()
186-
.decode(UpdatedContent.self, from: updatedData ?? Data())
187-
continuation.resume(updatedContent)
185+
if let updatedData {
186+
let updatedContent = try JSONDecoder()
187+
.decode(UpdatedContent.self, from: updatedData ?? Data())
188+
continuation.resume(updatedContent)
189+
} else {
190+
continuation.resume(nil)
191+
}
188192
} catch {
189193
continuation.reject(error)
190194
}

Core/Sources/CopilotService/CopilotService.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,21 @@ public class CopilotBaseService {
7575

7676
return InitializeParams(
7777
processId: Int(ProcessInfo.processInfo.processIdentifier),
78+
clientInfo: .init(name: "Copilot for Xcode"),
7879
locale: nil,
79-
rootPath: nil,
80+
rootPath: projectRoot.path,
8081
rootUri: projectRoot.path,
8182
initializationOptions: nil,
8283
capabilities: capabilities,
8384
trace: nil,
8485
workspaceFolders: nil
8586
)
8687
}
88+
89+
server.notificationHandler = { _, respond in
90+
respond(nil)
91+
}
92+
8793
return server
8894
}()
8995
}

Core/Sources/Service/AutoTrigger.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ actor AutoTrigger {
88
var eventObserver: CGEventObserverType = CGEventObserver()
99
var task: Task<Void, Error>?
1010

11-
private init() {}
11+
private init() {
12+
Task { @ServiceActor in
13+
while !Task.isCancelled {
14+
try await Task.sleep(nanoseconds: 8 * 60 * 60 * 1_000_000_000)
15+
for (url, workspace) in workspaces {
16+
if workspace.isExpired {
17+
workspaces[url] = nil
18+
} else {
19+
workspaces[url]?.cleanUp()
20+
}
21+
}
22+
}
23+
}
24+
}
1225

1326
func start(by listener: ObjectIdentifier) {
1427
listeners.insert(listener)
@@ -19,9 +32,14 @@ actor AutoTrigger {
1932
for await _ in stream {
2033
triggerTask?.cancel()
2134
if Task.isCancelled { break }
22-
triggerTask = Task {
35+
triggerTask = Task { @ServiceActor in
2336
try? await Task.sleep(nanoseconds: 2_000_000_000)
2437
if Task.isCancelled { return }
38+
let fileURL = try? await Environment.fetchCurrentFileURL()
39+
guard let folderURL = try? await Environment.fetchCurrentProjectRootURL(fileURL),
40+
let workspace = workspaces[folderURL],
41+
workspace.isRealtimeSuggestionEnabled
42+
else { return }
2543
try? await Environment.triggerAction("Realtime Suggestions")
2644
}
2745
}

Core/Sources/Service/Environment.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ private struct FailedToFetchFileURLError: Error, LocalizedError {
1717
enum Environment {
1818
static var now = { Date() }
1919

20-
static var fetchCurrentProjectRootURL: () async throws -> URL? = {
20+
static var fetchCurrentProjectRootURL: (_ fileURL: URL?) async throws -> URL? = { fileURL in
2121
let appleScript = """
2222
tell application "Xcode"
2323
return path of document of the first window
@@ -34,7 +34,20 @@ enum Environment {
3434
}
3535
return url
3636
}
37-
return nil
37+
38+
guard var currentURL = fileURL else { return nil }
39+
var firstDirectoryURL: URL? = nil
40+
while currentURL.pathComponents.count > 1 {
41+
defer { currentURL.deleteLastPathComponent() }
42+
guard FileManager.default.fileIsDirectory(atPath: currentURL.path) else { continue }
43+
if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
44+
let gitURL = currentURL.appendingPathComponent(".git")
45+
if FileManager.default.fileIsDirectory(atPath: gitURL.path) {
46+
return currentURL
47+
}
48+
}
49+
50+
return firstDirectoryURL ?? fileURL
3851
}
3952

4053
static var fetchCurrentFileURL: () async throws -> URL = {
@@ -75,8 +88,9 @@ enum Environment {
7588
)
7689
}
7790
}
78-
if let path {
79-
return URL(fileURLWithPath: path)
91+
if let path = path?.removingPercentEncoding {
92+
let url = URL(fileURLWithPath: path.replacingOccurrences(of: "file://", with: ""))
93+
return url
8094
}
8195
} catch {
8296
if let axError = error as? AXError, axError == .apiDisabled {
@@ -109,7 +123,7 @@ enum Environment {
109123

110124
guard let activeXcode = xcodes.first(where: { $0.isActive }) else { return }
111125
let bundleName = Bundle.main.object(forInfoDictionaryKey: "EXTENSION_BUNDLE_NAME") as! String
112-
126+
113127
/// check if menu is open, if not, click the menu item.
114128
let appleScript = """
115129
tell application "System Events"

Core/Sources/Service/Helpers.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import Foundation
33
extension FileManager {
44
func fileIsDirectory(atPath path: String) -> Bool {
55
var isDirectory: ObjCBool = false
6-
fileExists(atPath: path, isDirectory: &isDirectory)
7-
return isDirectory.boolValue
6+
let exists = fileExists(atPath: path, isDirectory: &isDirectory)
7+
return isDirectory.boolValue && exists
88
}
99
}
1010

0 commit comments

Comments
 (0)