forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAutoTrigger.swift
More file actions
110 lines (99 loc) · 4.37 KB
/
AutoTrigger.swift
File metadata and controls
110 lines (99 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import AppKit
import Foundation
import os.log
import XPCShared
public actor AutoTrigger {
public static let shared = AutoTrigger()
private var listeners = Set<AnyHashable>()
var eventObserver: CGEventObserverType = CGEventObserver()
var task: Task<Void, Error>?
private init() {
// Occasionally cleanup workspaces.
Task { @ServiceActor in
while !Task.isCancelled {
try await Task.sleep(nanoseconds: 8 * 60 * 60 * 1_000_000_000)
for (url, workspace) in workspaces {
if workspace.isExpired {
workspaces[url] = nil
} else {
workspaces[url]?.cleanUp()
}
}
}
}
// Start the auto trigger if Xcode is running.
Task {
for xcode in await Environment.runningXcodes() {
await start(by: xcode.processIdentifier)
}
let sequence = NSWorkspace.shared.notificationCenter
.notifications(named: NSWorkspace.didLaunchApplicationNotification)
for await notification in sequence {
try Task.checkCancellation()
guard let app = notification
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication
else { continue }
guard app.bundleIdentifier == "com.apple.dt.Xcode" else { continue }
await start(by: app.processIdentifier)
}
}
// Remove listener if Xcode is terminated.
Task {
let sequence = NSWorkspace.shared.notificationCenter
.notifications(named: NSWorkspace.didTerminateApplicationNotification)
for await notification in sequence {
try Task.checkCancellation()
guard let app = notification
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication
else { continue }
guard app.bundleIdentifier == "com.apple.dt.Xcode" else { continue }
await stop(by: app.processIdentifier)
}
}
}
func start(by listener: AnyHashable) {
os_log(.info, "Add auto trigger listener: %@.", listener as CVarArg)
listeners.insert(listener)
if task == nil {
task = Task { [stream = eventObserver.stream] in
var triggerTask: Task<Void, Error>?
for await eventType in stream {
triggerTask?.cancel()
if Task.isCancelled { break }
guard await Environment.isXcodeActive() else { continue }
await withTaskGroup(of: Void.self) { group in
for (_, workspace) in await workspaces {
group.addTask {
await workspace.cancelInFlightRealtimeSuggestionRequests()
}
}
}
guard eventType == .keyUp else { continue }
triggerTask = Task { @ServiceActor in
try? await Task.sleep(nanoseconds: 1_500_000_000)
if Task.isCancelled { return }
let fileURL = try? await Environment.fetchCurrentFileURL()
let folderURL = try? await Environment.fetchCurrentProjectRootURL(fileURL)
guard let workspaceURL = folderURL ?? fileURL else { return }
let workspace = workspaces[workspaceURL]
?? Workspace(projectRootURL: workspaceURL)
workspaces[workspaceURL] = workspace
guard workspace.isRealtimeSuggestionEnabled else { return }
if Task.isCancelled { return }
try? await Environment.triggerAction("Prefetch Suggestions")
}
}
}
}
eventObserver.activateIfPossible()
}
func stop(by listener: AnyHashable) {
os_log(.info, "Remove auto trigger listener: %@.", listener as CVarArg)
listeners.remove(listener)
guard listeners.isEmpty else { return }
os_log(.info, "Auto trigger is stopped.")
task?.cancel()
task = nil
eventObserver.deactivate()
}
}