Skip to content

Commit 824ad01

Browse files
committed
Add Service singleton to handle all other singletons in Service target
1 parent dee703a commit 824ad01

13 files changed

+168
-122
lines changed

Core/Sources/Service/DependencyUpdater.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import CodeiumService
22
import GitHubCopilotService
33
import Logger
44

5-
public struct DependencyUpdater {
6-
public init() {}
5+
struct DependencyUpdater {
6+
init() {}
77

8-
public func update() {
8+
func update() {
99
Task {
1010
await withTaskGroup(of: Void.self) { taskGroup in
1111
let gitHubCopilot = GitHubCopilotInstallationManager()

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ struct GUI: ReducerProtocol {
197197

198198
@MainActor
199199
public final class GraphicalUserInterfaceController {
200-
public static let shared = GraphicalUserInterfaceController()
201200
private let store: StoreOf<GUI>
202201
let widgetController: SuggestionWidgetController
203202
let widgetDataSource: WidgetDataSource
@@ -208,7 +207,7 @@ public final class GraphicalUserInterfaceController {
208207
weak var store: StoreOf<GUI>?
209208
}
210209

211-
private init() {
210+
init() {
212211
let chatTabPool = ChatTabPool()
213212
let suggestionDependency = SuggestionWidgetControllerDependency()
214213
let setupDependency: (inout DependencyValues) -> Void = { dependencies in

Core/Sources/Service/GUI/PromptToCodeProvider+Service.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ extension PromptToCodeProvider {
5656
}
5757

5858
onAcceptSuggestionTapped = {
59-
Task { @ServiceActor in
59+
Task {
6060
let handler = PseudoCommandHandler()
6161
await handler.acceptSuggestion()
6262
if let app = ActiveApplicationMonitor.previousActiveApplication, app.isXcode {

Core/Sources/Service/GUI/WidgetDataSource.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class WidgetDataSource {
2121
self.provider = provider
2222
}
2323
}
24-
24+
2525
private(set) var promptToCodes = [URL: PromptToCode]()
2626

2727
init() {}
@@ -87,7 +87,7 @@ final class WidgetDataSource {
8787

8888
extension WidgetDataSource: SuggestionWidgetDataSource {
8989
func suggestionForFile(at url: URL) async -> SuggestionProvider? {
90-
for workspace in await workspaces.values {
90+
for workspace in await Service.shared.workspacePool.workspaces.values {
9191
if let filespace = await workspace.filespaces[url],
9292
let suggestion = await filespace.presentingSuggestion
9393
{
@@ -98,19 +98,19 @@ extension WidgetDataSource: SuggestionWidgetDataSource {
9898
suggestionCount: filespace.suggestions.count,
9999
currentSuggestionIndex: filespace.suggestionIndex,
100100
onSelectPreviousSuggestionTapped: {
101-
Task { @ServiceActor in
101+
Task {
102102
let handler = PseudoCommandHandler()
103103
await handler.presentPreviousSuggestion()
104104
}
105105
},
106106
onSelectNextSuggestionTapped: {
107-
Task { @ServiceActor in
107+
Task {
108108
let handler = PseudoCommandHandler()
109109
await handler.presentNextSuggestion()
110110
}
111111
},
112112
onRejectSuggestionTapped: {
113-
Task { @ServiceActor in
113+
Task {
114114
let handler = PseudoCommandHandler()
115115
await handler.rejectSuggestions()
116116
if let app = ActiveApplicationMonitor.previousActiveApplication,
@@ -122,7 +122,7 @@ extension WidgetDataSource: SuggestionWidgetDataSource {
122122
}
123123
},
124124
onAcceptSuggestionTapped: {
125-
Task { @ServiceActor in
125+
Task {
126126
let handler = PseudoCommandHandler()
127127
await handler.acceptSuggestion()
128128
if let app = ActiveApplicationMonitor.previousActiveApplication,

Core/Sources/Service/RealtimeSuggestionController.swift

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import Foundation
99
import Logger
1010
import Preferences
1111
import QuartzCore
12+
import Workspace
1213
import XcodeInspector
1314

14-
@ServiceActor
15+
@WorkspaceActor
1516
public class RealtimeSuggestionController {
16-
public nonisolated static let shared = RealtimeSuggestionController()
1717
var eventObserver: CGEventObserverType = CGEventObserver(eventsOfInterest: [
1818
.keyUp,
1919
.keyDown,
@@ -28,12 +28,11 @@ public class RealtimeSuggestionController {
2828
private var focusedUIElement: AXUIElement?
2929
private var sourceEditor: SourceEditor?
3030

31-
private nonisolated init() {
31+
init() {
3232
Task { [weak self] in
33-
3433
if let app = ActiveApplicationMonitor.activeXcode {
35-
await self?.handleXcodeChanged(app)
36-
await self?.startHIDObservation(by: 1)
34+
self?.handleXcodeChanged(app)
35+
self?.startHIDObservation(by: 1)
3736
}
3837
var previousApp = ActiveApplicationMonitor.activeXcode
3938
for await app in ActiveApplicationMonitor.createStream() {
@@ -42,13 +41,13 @@ public class RealtimeSuggestionController {
4241
defer { previousApp = app }
4342

4443
if let app = ActiveApplicationMonitor.activeXcode, app != previousApp {
45-
await self.handleXcodeChanged(app)
44+
self.handleXcodeChanged(app)
4645
}
4746

4847
if ActiveApplicationMonitor.activeXcode != nil {
49-
await startHIDObservation(by: 1)
48+
startHIDObservation(by: 1)
5049
} else {
51-
await stopHIDObservation(by: 1)
50+
stopHIDObservation(by: 1)
5251
}
5352
}
5453
}
@@ -104,11 +103,12 @@ public class RealtimeSuggestionController {
104103
guard let focusElement = application.focusedElement else { return }
105104
let focusElementType = focusElement.description
106105
focusedUIElement = focusElement
107-
106+
108107
Task { // Notify suggestion service for open file.
109108
try await Task.sleep(nanoseconds: 500_000_000)
110109
let fileURL = try await Environment.fetchCurrentFileURL()
111-
_ = try await Workspace.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
110+
_ = try await Service.shared.workspacePool
111+
.fetchOrCreateWorkspaceAndFilespace(fileURL: fileURL)
112112
}
113113

114114
guard focusElementType == "Source Editor" else { return }
@@ -154,21 +154,21 @@ public class RealtimeSuggestionController {
154154
}
155155
}
156156

157-
Task { // Get cache ready for real-time suggestions.
157+
Task { @WorkspaceActor in // Get cache ready for real-time suggestions.
158158
guard UserDefaults.shared.value(for: \.preCacheOnFileOpen) else { return }
159159
let fileURL = try await Environment.fetchCurrentFileURL()
160-
let (_, filespace) = try await Workspace
161-
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
160+
let (_, filespace) = try await Service.shared.workspacePool
161+
.fetchOrCreateWorkspaceAndFilespace(fileURL: fileURL)
162162

163-
if filespace.uti == nil {
163+
if filespace.codeMetadata.uti == nil {
164164
Logger.service.info("Generate cache for file.")
165165
// avoid the command get called twice
166-
filespace.uti = ""
166+
filespace.codeMetadata.uti = ""
167167
do {
168168
try await Environment.triggerAction("Real-time Suggestions")
169169
} catch {
170-
if filespace.uti?.isEmpty ?? true {
171-
filespace.uti = nil
170+
if filespace.codeMetadata.uti?.isEmpty ?? true {
171+
filespace.codeMetadata.uti = nil
172172
}
173173
}
174174
}
@@ -191,7 +191,7 @@ public class RealtimeSuggestionController {
191191
}
192192

193193
func triggerPrefetchDebounced(force: Bool = false) {
194-
inflightPrefetchTask = Task { @ServiceActor in
194+
inflightPrefetchTask = Task { @WorkspaceActor in
195195
try? await Task.sleep(nanoseconds: UInt64((
196196
UserDefaults.shared.value(for: \.realtimeSuggestionDebounce)
197197
) * 1_000_000_000))
@@ -201,8 +201,8 @@ public class RealtimeSuggestionController {
201201

202202
if UserDefaults.shared.value(for: \.disableSuggestionFeatureGlobally),
203203
let fileURL = try? await Environment.fetchCurrentFileURL(),
204-
let (workspace, _) = try? await Workspace
205-
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
204+
let (workspace, _) = try? await Service.shared.workspacePool
205+
.fetchOrCreateWorkspaceAndFilespace(fileURL: fileURL)
206206
{
207207
let isEnabled = workspace.isSuggestionFeatureEnabled
208208
if !isEnabled { return }
@@ -221,7 +221,7 @@ public class RealtimeSuggestionController {
221221

222222
// cancel in-flight tasks
223223
await withTaskGroup(of: Void.self) { group in
224-
for (_, workspace) in workspaces {
224+
for (_, workspace) in Service.shared.workspacePool.workspaces {
225225
group.addTask {
226226
await workspace.cancelInFlightRealtimeSuggestionRequests()
227227
}
@@ -240,10 +240,10 @@ public class RealtimeSuggestionController {
240240

241241
func notifyEditingFileChange(editor: AXUIElement) async {
242242
guard let fileURL = try? await Environment.fetchCurrentFileURL(),
243-
let (workspace, filespace) = try? await Workspace
244-
.fetchOrCreateWorkspaceIfNeeded(fileURL: fileURL)
243+
let (workspace, filespace) = try? await Service.shared.workspacePool
244+
.fetchOrCreateWorkspaceAndFilespace(fileURL: fileURL)
245245
else { return }
246-
workspace.notifyUpdateFile(filespace: filespace, content: editor.value)
246+
workspace.suggestionPlugin?.notifyUpdateFile(filespace: filespace, content: editor.value)
247247
}
248248
}
249249

Core/Sources/Service/ScheduledCleaner.swift

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,20 @@ import AppKit
33
import AXExtension
44
import Foundation
55
import Logger
6+
import Workspace
67
import XcodeInspector
78

89
public final class ScheduledCleaner {
9-
public init() {
10+
let workspacePool: WorkspacePool
11+
let guiController: GraphicalUserInterfaceController
12+
13+
init(
14+
workspacePool: WorkspacePool,
15+
guiController: GraphicalUserInterfaceController
16+
) {
17+
self.workspacePool = workspacePool
18+
self.guiController = guiController
19+
1020
// occasionally cleanup workspaces.
1121
Task { @ServiceActor in
1222
while !Task.isCancelled {
@@ -43,38 +53,37 @@ public final class ScheduledCleaner {
4353
}
4454
}
4555
}
46-
for (url, workspace) in workspaces {
47-
if workspace.isExpired, workspaceInfos[.url(url)] == nil {
56+
for (url, workspace) in await workspacePool.workspaces {
57+
if await workspace.isExpired, workspaceInfos[.url(url)] == nil {
4858
Logger.service.info("Remove idle workspace")
49-
for url in workspace.filespaces.keys {
50-
await GraphicalUserInterfaceController.shared.widgetDataSource.cleanup(for: url)
59+
for url in await workspace.filespaces.keys {
60+
await guiController.widgetDataSource.cleanup(for: url)
5161
}
52-
workspace.cleanUp(availableTabs: [])
53-
workspaces[url] = nil
62+
await workspace.cleanUp(availableTabs: [])
63+
await workspacePool.removeWorkspace(url: url)
5464
} else {
5565
let tabs = (workspaceInfos[.url(url)]?.tabs ?? [])
5666
.union(workspaceInfos[.unknown]?.tabs ?? [])
5767
// cleanup chats for unused files
58-
let filespaces = workspace.filespaces
68+
let filespaces = await workspace.filespaces
5969
for (url, _) in filespaces {
60-
if workspace.isFilespaceExpired(
70+
if await workspace.isFilespaceExpired(
6171
fileURL: url,
6272
availableTabs: tabs
6373
) {
6474
Logger.service.info("Remove idle filespace")
65-
await GraphicalUserInterfaceController.shared.widgetDataSource
66-
.cleanup(for: url)
75+
await guiController.widgetDataSource.cleanup(for: url)
6776
}
6877
}
6978
// cleanup workspace
70-
workspace.cleanUp(availableTabs: tabs)
79+
await workspace.cleanUp(availableTabs: tabs)
7180
}
7281
}
7382
}
7483

7584
@ServiceActor
7685
public func closeAllChildProcesses() async {
77-
for (_, workspace) in workspaces {
86+
for (_, workspace) in await workspacePool.workspaces {
7887
await workspace.terminateSuggestionService()
7988
}
8089
}

Core/Sources/Service/Service.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
import Workspace
3+
4+
@globalActor public enum ServiceActor {
5+
public actor TheActor {}
6+
public static let shared = TheActor()
7+
}
8+
9+
/// The running extension service.
10+
public final class Service {
11+
public static let shared = Service()
12+
13+
@WorkspaceActor
14+
let workspacePool = {
15+
let it = WorkspacePool()
16+
it.registerPlugin {
17+
SuggestionServiceWorkspacePlugin(workspace: $0)
18+
}
19+
return it
20+
}()
21+
@MainActor
22+
public let guiController = GraphicalUserInterfaceController()
23+
@WorkspaceActor
24+
public let realtimeSuggestionController = RealtimeSuggestionController()
25+
public let scheduledCleaner: ScheduledCleaner
26+
27+
private init() {
28+
scheduledCleaner = .init(workspacePool: workspacePool, guiController: guiController)
29+
DependencyUpdater().update()
30+
}
31+
}
32+

0 commit comments

Comments
 (0)