Skip to content

Commit 0266337

Browse files
committed
Make XcodeInspector a bit more thread safe
1 parent ec0da25 commit 0266337

File tree

7 files changed

+64
-51
lines changed

7 files changed

+64
-51
lines changed

Core/Sources/PromptToCodeService/OpenAIPromptToCodeService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public final class OpenAIPromptToCodeService: PromptToCodeServiceType {
3131
return userPreferredLanguage.isEmpty ? "" : " in \(userPreferredLanguage)"
3232
}()
3333

34-
let editor: EditorInformation = XcodeInspector.shared.getFocusedEditorContent() ?? .init(
34+
let editor: EditorInformation = await XcodeInspector.shared.getFocusedEditorContent() ?? .init(
3535
editorContent: .init(
3636
content: source.content,
3737
lines: source.lines,

ExtensionService/AppDelegate+Menu.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ extension AppDelegate: NSMenuDelegate {
218218

219219
private extension AppDelegate {
220220
@objc func restartXcodeInspector() {
221-
XcodeInspector.shared.restart(cleanUp: true)
221+
Task {
222+
await XcodeInspector.shared.restart(cleanUp: true)
223+
}
222224
}
223225

224226
@objc func reactivateObservationsToXcode() {

Pro

Submodule Pro updated from 528831a to 6bc4b4c

Tool/Sources/ChatContextCollectors/ActiveDocumentChatContextCollector/ActiveDocumentChatContextCollector.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public final class ActiveDocumentChatContextCollector: ChatContextCollector {
2222
content: String,
2323
configuration: ChatGPTConfiguration
2424
) async -> ChatContext {
25-
guard let info = getEditorInformation() else { return .empty }
25+
guard let info = await XcodeInspector.shared.getFocusedEditorContent()
26+
else { return .empty }
2627
let context = getActiveDocumentContext(info)
2728
activeDocumentContext = context
2829

Tool/Sources/ChatContextCollectors/ActiveDocumentChatContextCollector/GetEditorInfo.swift

Lines changed: 0 additions & 8 deletions
This file was deleted.

Tool/Sources/ChatContextCollectors/ActiveDocumentChatContextCollector/LegacyActiveDocumentChatContextCollector.swift

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ public struct LegacyActiveDocumentChatContextCollector: ChatContextCollector {
1313
scopes: Set<ChatContext.Scope>,
1414
content: String,
1515
configuration: ChatGPTConfiguration
16-
) -> ChatContext {
17-
guard let content = getEditorInformation() else { return .empty }
16+
) async -> ChatContext {
17+
guard let content = await XcodeInspector.shared.getFocusedEditorContent()
18+
else { return .empty }
1819
let relativePath = content.relativePath
1920
let selectionRange = content.editorContent?.selections.first ?? .outOfScope
2021
let editorContent = {
@@ -79,26 +80,26 @@ public struct LegacyActiveDocumentChatContextCollector: ChatContextCollector {
7980

8081
return .init(
8182
systemPrompt: """
82-
Active Document Context:###
83-
Document Relative Path: \(relativePath)
84-
Selection Range Start: \
85-
Line \(selectionRange.start.line) \
86-
Character \(selectionRange.start.character)
87-
Selection Range End: \
88-
Line \(selectionRange.end.line) \
89-
Character \(selectionRange.end.character)
90-
Cursor Position: \
91-
Line \(selectionRange.end.line) \
92-
Character \(selectionRange.end.character)
93-
\(editorContent)
94-
Line Annotations:
95-
\(
96-
content.editorContent?.lineAnnotations
97-
.map { " - \($0)" }
98-
.joined(separator: "\n") ?? "N/A"
99-
)
100-
###
101-
""",
83+
Active Document Context:###
84+
Document Relative Path: \(relativePath)
85+
Selection Range Start: \
86+
Line \(selectionRange.start.line) \
87+
Character \(selectionRange.start.character)
88+
Selection Range End: \
89+
Line \(selectionRange.end.line) \
90+
Character \(selectionRange.end.character)
91+
Cursor Position: \
92+
Line \(selectionRange.end.line) \
93+
Character \(selectionRange.end.character)
94+
\(editorContent)
95+
Line Annotations:
96+
\(
97+
content.editorContent?.lineAnnotations
98+
.map { " - \($0)" }
99+
.joined(separator: "\n") ?? "N/A"
100+
)
101+
###
102+
""",
102103
retrievedContent: [],
103104
functions: []
104105
)

Tool/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,26 @@ public enum XcodeInspectorActor: GlobalActor {
2020

2121
public final class XcodeInspector: ObservableObject {
2222
public static let shared = XcodeInspector()
23+
24+
@XcodeInspectorActor
25+
@dynamicMemberLookup
26+
public class Safe {
27+
var inspector: XcodeInspector { .shared }
28+
nonisolated init() {}
29+
public subscript<T>(dynamicMember member: KeyPath<XcodeInspector, T>) -> T {
30+
inspector[keyPath: member]
31+
}
32+
}
2333

2434
private var toast: ToastController { ToastControllerDependencyKey.liveValue }
2535

2636
private var cancellable = Set<AnyCancellable>()
2737
private var activeXcodeObservations = Set<Task<Void, Error>>()
2838
private var appChangeObservations = Set<Task<Void, Never>>()
2939
private var activeXcodeCancellable = Set<AnyCancellable>()
40+
41+
#warning("TODO: Find a good way to make XcodeInspector thread safe!")
42+
public var safe = Safe()
3043

3144
@Published public fileprivate(set) var activeApplication: AppInstanceInspector?
3245
@Published public fileprivate(set) var previousActiveApplication: AppInstanceInspector?
@@ -45,13 +58,14 @@ public final class XcodeInspector: ObservableObject {
4558
///
4659
/// - note: This method is expensive. It needs to convert index based ranges to line based
4760
/// ranges.
48-
public func getFocusedEditorContent() -> EditorInformation? {
49-
guard let documentURL = XcodeInspector.shared.realtimeActiveDocumentURL,
50-
let workspaceURL = XcodeInspector.shared.realtimeActiveWorkspaceURL,
51-
let projectURL = XcodeInspector.shared.activeProjectRootURL
61+
@XcodeInspectorActor
62+
public func getFocusedEditorContent() async -> EditorInformation? {
63+
guard let documentURL = realtimeActiveDocumentURL,
64+
let workspaceURL = realtimeActiveWorkspaceURL,
65+
let projectURL = activeProjectRootURL
5266
else { return nil }
5367

54-
let editorContent = XcodeInspector.shared.focusedEditor?.getContent()
68+
let editorContent = focusedEditor?.getContent()
5569
let language = languageIdentifierFromFileURL(documentURL)
5670
let relativePath = documentURL.path.replacingOccurrences(of: projectURL.path, with: "")
5771

@@ -98,9 +112,12 @@ public final class XcodeInspector: ObservableObject {
98112

99113
init() {
100114
AXUIElement.setGlobalMessagingTimeout(3)
101-
restart()
115+
Task { @XcodeInspectorActor in
116+
restart()
117+
}
102118
}
103119

120+
@XcodeInspectorActor
104121
public func restart(cleanUp: Bool = false) {
105122
if cleanUp {
106123
activeXcodeObservations.forEach { $0.cancel() }
@@ -135,7 +152,7 @@ public final class XcodeInspector: ObservableObject {
135152
let appChangeTask = Task(priority: .utility) { [weak self] in
136153
guard let self else { return }
137154
if let activeXcode {
138-
await setActiveXcode(activeXcode)
155+
setActiveXcode(activeXcode)
139156
}
140157

141158
await withThrowingTaskGroup(of: Void.self) { [weak self] group in
@@ -318,24 +335,24 @@ public final class XcodeInspector: ObservableObject {
318335
checkForAccessibilityMalfunction("Reactivate Xcode")
319336
}
320337

321-
xcode.$completionPanel.receive(on: DispatchQueue.main).sink { [weak self] element in
322-
self?.completionPanel = element
338+
xcode.$completionPanel.sink { [weak self] element in
339+
Task { @XcodeInspectorActor in self?.completionPanel = element }
323340
}.store(in: &activeXcodeCancellable)
324341

325-
xcode.$documentURL.receive(on: DispatchQueue.main).sink { [weak self] url in
326-
self?.activeDocumentURL = url
342+
xcode.$documentURL.sink { [weak self] url in
343+
Task { @XcodeInspectorActor in self?.activeDocumentURL = url }
327344
}.store(in: &activeXcodeCancellable)
328345

329-
xcode.$workspaceURL.receive(on: DispatchQueue.main).sink { [weak self] url in
330-
self?.activeWorkspaceURL = url
346+
xcode.$workspaceURL.sink { [weak self] url in
347+
Task { @XcodeInspectorActor in self?.activeWorkspaceURL = url }
331348
}.store(in: &activeXcodeCancellable)
332349

333-
xcode.$projectRootURL.receive(on: DispatchQueue.main).sink { [weak self] url in
334-
self?.activeProjectRootURL = url
350+
xcode.$projectRootURL.sink { [weak self] url in
351+
Task { @XcodeInspectorActor in self?.activeProjectRootURL = url }
335352
}.store(in: &activeXcodeCancellable)
336353

337-
xcode.$focusedWindow.receive(on: DispatchQueue.main).sink { [weak self] window in
338-
self?.focusedWindow = window
354+
xcode.$focusedWindow.sink { [weak self] window in
355+
Task { @XcodeInspectorActor in self?.focusedWindow = window }
339356
}.store(in: &activeXcodeCancellable)
340357
}
341358

0 commit comments

Comments
 (0)