@@ -13,20 +13,21 @@ public final class XcodeInspector: ObservableObject {
1313
1414 private var cancellable = Set < AnyCancellable > ( )
1515 private var activeXcodeObservations = Set < Task < Void , Error > > ( )
16+ private var appChangeObservations = Set < Task < Void , Never > > ( )
1617 private var activeXcodeCancellable = Set < AnyCancellable > ( )
1718
18- @Published public internal ( set) var activeApplication : AppInstanceInspector ?
19- @Published public internal ( set) var previousActiveApplication : AppInstanceInspector ?
20- @Published public internal ( set) var activeXcode : XcodeAppInstanceInspector ?
21- @Published public internal ( set) var latestActiveXcode : XcodeAppInstanceInspector ?
22- @Published public internal ( set) var xcodes : [ XcodeAppInstanceInspector ] = [ ]
23- @Published public internal ( set) var activeProjectRootURL : URL ? = nil
24- @Published public internal ( set) var activeDocumentURL : URL ? = nil
25- @Published public internal ( set) var activeWorkspaceURL : URL ? = nil
26- @Published public internal ( set) var focusedWindow : XcodeWindowInspector ?
27- @Published public internal ( set) var focusedEditor : SourceEditor ?
28- @Published public internal ( set) var focusedElement : AXUIElement ?
29- @Published public internal ( set) var completionPanel : AXUIElement ?
19+ @Published public fileprivate ( set) var activeApplication : AppInstanceInspector ?
20+ @Published public fileprivate ( set) var previousActiveApplication : AppInstanceInspector ?
21+ @Published public fileprivate ( set) var activeXcode : XcodeAppInstanceInspector ?
22+ @Published public fileprivate ( set) var latestActiveXcode : XcodeAppInstanceInspector ?
23+ @Published public fileprivate ( set) var xcodes : [ XcodeAppInstanceInspector ] = [ ]
24+ @Published public fileprivate ( set) var activeProjectRootURL : URL ? = nil
25+ @Published public fileprivate ( set) var activeDocumentURL : URL ? = nil
26+ @Published public fileprivate ( set) var activeWorkspaceURL : URL ? = nil
27+ @Published public fileprivate ( set) var focusedWindow : XcodeWindowInspector ?
28+ @Published public fileprivate ( set) var focusedEditor : SourceEditor ?
29+ @Published public fileprivate ( set) var focusedElement : AXUIElement ?
30+ @Published public fileprivate ( set) var completionPanel : AXUIElement ?
3031
3132 public var focusedEditorContent : EditorInformation ? {
3233 guard let documentURL = XcodeInspector . shared. realtimeActiveDocumentURL,
@@ -79,6 +80,10 @@ public final class XcodeInspector: ObservableObject {
7980 latestActiveXcode? . realtimeProjectURL ?? activeProjectRootURL
8081 }
8182
83+ init ( ) {
84+ restart ( )
85+ }
86+
8287 public func restart( cleanUp: Bool = false ) {
8388 if cleanUp {
8489 activeXcodeObservations. forEach { $0. cancel ( ) }
@@ -96,7 +101,7 @@ public final class XcodeInspector: ObservableObject {
96101 focusedElement = nil
97102 completionPanel = nil
98103 }
99-
104+
100105 let runningApplications = NSWorkspace . shared. runningApplications
101106 xcodes = runningApplications
102107 . filter { $0. isXcode }
@@ -106,76 +111,85 @@ public final class XcodeInspector: ObservableObject {
106111 activeApplication = activeXcode ?? runningApplications
107112 . first ( where: \. isActive)
108113 . map ( AppInstanceInspector . init ( runningApplication: ) )
109- }
110-
111- init ( ) {
112- restart ( )
114+
115+ appChangeObservations. forEach { $0. cancel ( ) }
116+ appChangeObservations. removeAll ( )
113117
114- Task { // Did activate app
118+ let appChangeTask = Task { [ weak self] in
119+ guard let self else { return }
115120 if let activeXcode {
116121 await setActiveXcode ( activeXcode)
117122 }
118-
119- let sequence = NSWorkspace . shared. notificationCenter
120- . notifications ( named: NSWorkspace . didActivateApplicationNotification)
121- for await notification in sequence {
122- try Task . checkCancellation ( )
123- guard let app = notification
124- . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication
125- else { continue }
126- if app. isXcode {
127- if let existed = xcodes. first ( where: {
128- $0. runningApplication. processIdentifier == app. processIdentifier
129- } ) {
130- await MainActor . run {
131- setActiveXcode ( existed)
132- }
133- } else {
134- let new = XcodeAppInstanceInspector ( runningApplication: app)
135- await MainActor . run {
136- xcodes. append ( new)
137- setActiveXcode ( new)
123+
124+ await withThrowingTaskGroup ( of: Void . self) { [ weak self] group in
125+ group. addTask { [ weak self] in // Did activate app
126+ let sequence = NSWorkspace . shared. notificationCenter
127+ . notifications ( named: NSWorkspace . didActivateApplicationNotification)
128+ for await notification in sequence {
129+ try Task . checkCancellation ( )
130+ guard let self else { return }
131+ guard let app = notification
132+ . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication
133+ else { continue }
134+ if app. isXcode {
135+ if let existed = xcodes. first ( where: {
136+ $0. runningApplication. processIdentifier == app. processIdentifier
137+ } ) {
138+ await MainActor . run {
139+ self . setActiveXcode ( existed)
140+ }
141+ } else {
142+ let new = XcodeAppInstanceInspector ( runningApplication: app)
143+ await MainActor . run {
144+ self . xcodes. append ( new)
145+ self . setActiveXcode ( new)
146+ }
147+ }
148+ } else {
149+ let appInspector = AppInstanceInspector ( runningApplication: app)
150+ await MainActor . run {
151+ self . previousActiveApplication = self . activeApplication
152+ self . activeApplication = appInspector
153+ }
138154 }
139155 }
140- } else {
141- let appInspector = AppInstanceInspector ( runningApplication: app)
142- await MainActor . run {
143- previousActiveApplication = activeApplication
144- activeApplication = appInspector
145- }
146156 }
147- }
148- }
157+
158+ group. addTask { [ weak self] in // Did terminate app
159+ let sequence = NSWorkspace . shared. notificationCenter
160+ . notifications ( named: NSWorkspace . didTerminateApplicationNotification)
161+ for await notification in sequence {
162+ try Task . checkCancellation ( )
163+ guard let self else { return }
164+ guard let app = notification
165+ . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication
166+ else { continue }
167+ if app. isXcode {
168+ let processIdentifier = app. processIdentifier
169+ await MainActor . run {
170+ self . xcodes. removeAll {
171+ $0. runningApplication. processIdentifier == processIdentifier
172+ }
173+ if self . latestActiveXcode? . runningApplication
174+ . processIdentifier == processIdentifier
175+ {
176+ self . latestActiveXcode = nil
177+ }
149178
150- Task { // Did terminate app
151- let sequence = NSWorkspace . shared. notificationCenter
152- . notifications ( named: NSWorkspace . didTerminateApplicationNotification)
153- for await notification in sequence {
154- try Task . checkCancellation ( )
155- guard let app = notification
156- . userInfo ? [ NSWorkspace . applicationUserInfoKey] as? NSRunningApplication
157- else { continue }
158- if app. isXcode {
159- let processIdentifier = app. processIdentifier
160- await MainActor . run {
161- xcodes. removeAll {
162- $0. runningApplication. processIdentifier == processIdentifier
163- }
164- if latestActiveXcode? . runningApplication
165- . processIdentifier == processIdentifier
166- {
167- latestActiveXcode = nil
168- }
169-
170- if let activeXcode = xcodes. first ( where: \. isActive) {
171- setActiveXcode ( activeXcode)
179+ if let activeXcode = self . xcodes. first ( where: \. isActive) {
180+ self . setActiveXcode ( activeXcode)
181+ }
182+ }
172183 }
173184 }
174185 }
175186 }
176187 }
188+
189+ appChangeObservations. insert ( appChangeTask)
177190 }
178191
192+
179193 @MainActor
180194 func setActiveXcode( _ xcode: XcodeAppInstanceInspector ) {
181195 previousActiveApplication = activeApplication
@@ -229,23 +243,23 @@ public final class XcodeInspector: ObservableObject {
229243
230244 activeXcodeObservations. insert ( focusedElementChanged)
231245
232- xcode. $completionPanel. sink { [ weak self] element in
246+ xcode. $completionPanel. receive ( on : DispatchQueue . main ) . sink { [ weak self] element in
233247 self ? . completionPanel = element
234248 } . store ( in: & activeXcodeCancellable)
235249
236- xcode. $documentURL. sink { [ weak self] url in
250+ xcode. $documentURL. receive ( on : DispatchQueue . main ) . sink { [ weak self] url in
237251 self ? . activeDocumentURL = url
238252 } . store ( in: & activeXcodeCancellable)
239253
240- xcode. $workspaceURL. sink { [ weak self] url in
254+ xcode. $workspaceURL. receive ( on : DispatchQueue . main ) . sink { [ weak self] url in
241255 self ? . activeWorkspaceURL = url
242256 } . store ( in: & activeXcodeCancellable)
243257
244- xcode. $projectRootURL. sink { [ weak self] url in
258+ xcode. $projectRootURL. receive ( on : DispatchQueue . main ) . sink { [ weak self] url in
245259 self ? . activeProjectRootURL = url
246260 } . store ( in: & activeXcodeCancellable)
247261
248- xcode. $focusedWindow. sink { [ weak self] window in
262+ xcode. $focusedWindow. receive ( on : DispatchQueue . main ) . sink { [ weak self] window in
249263 self ? . focusedWindow = window
250264 } . store ( in: & activeXcodeCancellable)
251265 }
0 commit comments