Skip to content

Commit 2c15bc6

Browse files
committed
Merge branch 'feature/realtime-suggestion-blocking-tweak' into develop
2 parents b75c318 + fe2c477 commit 2c15bc6

File tree

10 files changed

+262
-120
lines changed

10 files changed

+262
-120
lines changed

Core/Sources/Service/AutoTrigger.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,29 @@ actor AutoTrigger {
2828
if task == nil {
2929
task = Task { [stream = eventObserver.stream] in
3030
var triggerTask: Task<Void, Error>?
31-
try? await Environment.triggerAction("Realtime Suggestions")
31+
try? await Environment.triggerAction("Real-time Suggestions")
3232
for await _ in stream {
3333
triggerTask?.cancel()
3434
if Task.isCancelled { break }
35+
guard await Environment.isXcodeActive() else { continue }
36+
37+
await withTaskGroup(of: Void.self) { group in
38+
for (_, workspace) in await workspaces {
39+
group.addTask {
40+
await workspace.cancelInFlightRealtimeSuggestionRequests()
41+
}
42+
}
43+
}
44+
3545
triggerTask = Task { @ServiceActor in
36-
try? await Task.sleep(nanoseconds: 2_000_000_000)
46+
try? await Task.sleep(nanoseconds: 2_500_000_000)
3747
if Task.isCancelled { return }
3848
let fileURL = try? await Environment.fetchCurrentFileURL()
3949
guard let folderURL = try? await Environment.fetchCurrentProjectRootURL(fileURL),
4050
let workspace = workspaces[folderURL],
4151
workspace.isRealtimeSuggestionEnabled
4252
else { return }
43-
try? await Environment.triggerAction("Realtime Suggestions")
53+
try? await Environment.triggerAction("Real-time Suggestions")
4454
}
4555
}
4656
}

Core/Sources/Service/CGEventObserver.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ final class CGEventObserver: CGEventObserverType {
1515

1616
private var continuation: AsyncStream<Void>.Continuation
1717
private var port: CFMachPort?
18-
private let eventsOfInterest: Set<CGEventType> = [.keyUp, .leftMouseUp, .mouseMoved]
18+
private let eventsOfInterest: Set<CGEventType> = [.keyUp, .leftMouseUp, .rightMouseUp]
1919
private let tapLocation: CGEventTapLocation = .cghidEventTap
2020
private let tapPlacement: CGEventTapPlacement = .tailAppendEventTap
2121
private let tapOptions: CGEventTapOptions = .listenOnly

Core/Sources/Service/Environment.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@ private struct FailedToFetchFileURLError: Error, LocalizedError {
1616

1717
enum Environment {
1818
static var now = { Date() }
19+
20+
static var isXcodeActive: () async -> Bool = {
21+
var activeXcodes = [NSRunningApplication]()
22+
var retryCount = 0
23+
// Sometimes runningApplications returns 0 items.
24+
while activeXcodes.isEmpty, retryCount < 3 {
25+
activeXcodes = NSRunningApplication
26+
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
27+
.sorted { lhs, _ in
28+
if lhs.isActive { return true }
29+
return false
30+
}
31+
if retryCount > 0 { try? await Task.sleep(nanoseconds: 1_000_000) }
32+
retryCount += 1
33+
}
34+
return !activeXcodes.isEmpty
35+
}
1936

2037
static var fetchCurrentProjectRootURL: (_ fileURL: URL?) async throws -> URL? = { fileURL in
2138
let appleScript = """
@@ -24,7 +41,8 @@ enum Environment {
2441
end tell
2542
"""
2643

27-
if let path = try await runAppleScript(appleScript) {
44+
let path = try await runAppleScript(appleScript)
45+
if !path.isEmpty {
2846
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
2947
var url = URL(fileURLWithPath: trimmedNewLine)
3048
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
@@ -61,7 +79,7 @@ enum Environment {
6179
if lhs.isActive { return true }
6280
return false
6381
}
64-
if retryCount > 0 { try await Task.sleep(nanoseconds: 50_000_000) }
82+
if retryCount > 0 { try await Task.sleep(nanoseconds: 10_000_000) }
6583
retryCount += 1
6684
}
6785

@@ -117,7 +135,7 @@ enum Environment {
117135
while xcodes.isEmpty, retryCount < 5 {
118136
xcodes = NSRunningApplication
119137
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
120-
if retryCount > 0 { try await Task.sleep(nanoseconds: 50_000_000) }
138+
if retryCount > 0 { try await Task.sleep(nanoseconds: 10_000_000) }
121139
retryCount += 1
122140
}
123141

Core/Sources/Service/Helpers.swift

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import LanguageServerProtocol
23

34
extension FileManager {
45
func fileIsDirectory(atPath path: String) -> Bool {
@@ -9,19 +10,86 @@ extension FileManager {
910
}
1011

1112
@discardableResult
12-
func runAppleScript(_ appleScript: String) async throws -> String? {
13+
func runAppleScript(_ appleScript: String) async throws -> String {
1314
let task = Process()
1415
task.launchPath = "/usr/bin/osascript"
1516
task.arguments = ["-e", appleScript]
1617
let outpipe = Pipe()
1718
task.standardOutput = outpipe
18-
try task.run()
19-
await Task.yield()
20-
task.waitUntilExit()
21-
if let data = try outpipe.fileHandleForReading.readToEnd(),
22-
let content = String(data: data, encoding: .utf8)
23-
{
24-
return content
19+
20+
return try await withUnsafeThrowingContinuation { continuation in
21+
do {
22+
task.terminationHandler = { _ in
23+
do {
24+
if let data = try outpipe.fileHandleForReading.readToEnd(),
25+
let content = String(data: data, encoding: .utf8)
26+
{
27+
continuation.resume(returning: content)
28+
return
29+
}
30+
continuation.resume(returning: "")
31+
} catch {
32+
continuation.resume(throwing: error)
33+
}
34+
}
35+
try task.run()
36+
} catch {
37+
continuation.resume(throwing: error)
38+
}
39+
}
40+
}
41+
42+
extension XPCService {
43+
@ServiceActor
44+
func fetchOrCreateWorkspaceIfNeeded(fileURL: URL) async throws -> Workspace {
45+
let projectURL = try await Environment.fetchCurrentProjectRootURL(fileURL)
46+
let workspaceURL = projectURL ?? fileURL
47+
let workspace = workspaces[workspaceURL] ?? Workspace(projectRootURL: workspaceURL)
48+
workspaces[workspaceURL] = workspace
49+
return workspace
50+
}
51+
}
52+
53+
extension NSError {
54+
static func from(_ error: Error) -> NSError {
55+
if let error = error as? ServerError {
56+
var message = "Unknown"
57+
switch error {
58+
case let .handlerUnavailable(handler):
59+
message = "Handler unavailable: \(handler)."
60+
case let .unhandledMethod(method):
61+
message = "Methond unhandled: \(method)."
62+
case let .notificationDispatchFailed(error):
63+
message = "Notification dispatch failed: \(error.localizedDescription)."
64+
case let .requestDispatchFailed(error):
65+
message = "Request dispatch failed: \(error.localizedDescription)."
66+
case let .clientDataUnavailable(error):
67+
message = "Client data unavalable: \(error.localizedDescription)."
68+
case .serverUnavailable:
69+
message = "Server unavailable, please make sure you have installed Node."
70+
case .missingExpectedParameter:
71+
message = "Missing expected parameter."
72+
case .missingExpectedResult:
73+
message = "Missing expected result."
74+
case let .unableToDecodeRequest(error):
75+
message = "Unable to decode request: \(error.localizedDescription)."
76+
case let .unableToSendRequest(error):
77+
message = "Unable to send request: \(error.localizedDescription)."
78+
case let .unableToSendNotification(error):
79+
message = "Unable to send notification: \(error.localizedDescription)."
80+
case let .serverError(code, m, _):
81+
message = "Server error: (\(code)) \(m)."
82+
case let .invalidRequest(error):
83+
message = "Invalid request: \(error?.localizedDescription ?? "Unknown")."
84+
case .timeout:
85+
message = "Timeout."
86+
}
87+
return NSError(domain: "com.intii.CopilotForXcode", code: -1, userInfo: [
88+
NSLocalizedDescriptionKey: message,
89+
])
90+
}
91+
return NSError(domain: "com.intii.CopilotForXcode", code: -1, userInfo: [
92+
NSLocalizedDescriptionKey: error.localizedDescription,
93+
])
2594
}
26-
return nil
2795
}

0 commit comments

Comments
 (0)