Skip to content

Commit 0c56c85

Browse files
committed
Restart observations when XcodeInspector restarts
1 parent 7b18d0f commit 0c56c85

1 file changed

Lines changed: 87 additions & 73 deletions

File tree

Tool/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 87 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)