Skip to content

Commit 769eafd

Browse files
committed
Replace AsyncPassthroughSubject from AsyncExtension
1 parent ecc4e9d commit 769eafd

File tree

9 files changed

+87
-21
lines changed

9 files changed

+87
-21
lines changed

Core/Sources/Service/RealtimeSuggestionController.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ public actor RealtimeSuggestionController {
6565
)
6666
}
6767

68-
let valueChange = notificationsFromEditor.filter { $0.kind == .valueChanged }
69-
let selectedTextChanged = notificationsFromEditor
68+
let valueChange = await notificationsFromEditor.notifications()
69+
.filter { $0.kind == .valueChanged }
70+
let selectedTextChanged = await notificationsFromEditor.notifications()
7071
.filter { $0.kind == .selectedTextChanged }
7172

7273
await withTaskGroup(of: Void.self) { [weak self] group in

Core/Sources/SuggestionWidget/WidgetWindowsController.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ private extension WidgetWindowsController {
288288
await windows.orderFront()
289289

290290
let documentURL = await MainActor.run { store.withState { $0.focusingDocumentURL } }
291-
for await notification in notifications {
291+
for await notification in await notifications.notifications() {
292292
try Task.checkCancellation()
293293

294294
/// Hide the widgets before switching to another window/editor
@@ -337,9 +337,9 @@ private extension WidgetWindowsController {
337337
func observe(to editor: SourceEditor) {
338338
observeToFocusedEditorTask?.cancel()
339339
observeToFocusedEditorTask = Task {
340-
let selectionRangeChange = editor.axNotifications
340+
let selectionRangeChange = await editor.axNotifications.notifications()
341341
.filter { $0.kind == .selectedTextChanged }
342-
let scroll = editor.axNotifications
342+
let scroll = await editor.axNotifications.notifications()
343343
.filter { $0.kind == .scrollPositionChanged }
344344

345345
if #available(macOS 13.0, *) {

Pro

Submodule Pro updated from 6b7c790 to 8c43137

Tool/Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ let package = Package(
4343
),
4444
.library(name: "GitIgnoreCheck", targets: ["GitIgnoreCheck"]),
4545
.library(name: "DebounceFunction", targets: ["DebounceFunction"]),
46+
.library(name: "AsyncPassthroughSubject", targets: ["AsyncPassthroughSubject"]),
4647
],
4748
dependencies: [
4849
// A fork of https://github.com/aespinilla/Tiktoken to allow loading from local files.
@@ -64,7 +65,6 @@ let package = Package(
6465
.package(url: "https://github.com/GottaGetSwifty/CodableWrappers", from: "2.0.7"),
6566
.package(url: "https://github.com/krzyzanowskim/STTextView", from: "0.8.21"),
6667
.package(url: "https://github.com/google/generative-ai-swift", from: "0.4.4"),
67-
.package(url: "https://github.com/sideeffect-io/AsyncExtensions", from: "0.5.2"),
6868

6969
// TreeSitter
7070
.package(url: "https://github.com/intitni/SwiftTreeSitter.git", branch: "main"),
@@ -173,7 +173,7 @@ let package = Package(
173173
"Logger",
174174
"Toast",
175175
"Preferences",
176-
.product(name: "AsyncExtensions", package: "AsyncExtensions"),
176+
"AsyncPassthroughSubject",
177177
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
178178
]
179179
),
@@ -182,6 +182,8 @@ let package = Package(
182182

183183
.target(name: "UserDefaultsObserver"),
184184

185+
.target(name: "AsyncPassthroughSubject"),
186+
185187
.target(
186188
name: "SharedUIComponents",
187189
dependencies: [
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import AppKit
2+
import Foundation
3+
4+
/// It uses notification center to mimic the behavior of a passthrough subject.
5+
public actor AsyncPassthroughSubject<Element> {
6+
let name: Notification.Name
7+
var tasks: [AsyncStream<Element>.Continuation] = []
8+
9+
deinit {
10+
tasks.forEach { $0.finish() }
11+
}
12+
13+
public init() {
14+
name = NSNotification.Name(
15+
"AsyncPassthroughSubject-\(UUID().uuidString)-\(String(describing: Element.self))"
16+
)
17+
}
18+
19+
public func notifications() -> AsyncStream<Element> {
20+
AsyncStream { [weak self, name] continuation in
21+
let task = Task { [weak self] in
22+
await self?.storeContinuation(continuation)
23+
let notifications = NSWorkspace.shared.notificationCenter.notifications(named: name)
24+
.compactMap {
25+
$0.object as? Element
26+
}
27+
for await notification in notifications {
28+
try Task.checkCancellation()
29+
guard self != nil else {
30+
continuation.finish()
31+
return
32+
}
33+
continuation.yield(notification)
34+
}
35+
}
36+
37+
continuation.onTermination = { termination in
38+
task.cancel()
39+
}
40+
}
41+
}
42+
43+
nonisolated
44+
public func send(_ element: Element) {
45+
Task { await _send(element) }
46+
}
47+
48+
func _send(_ element: Element) {
49+
NSWorkspace.shared.notificationCenter.post(name: name, object: element)
50+
}
51+
52+
func storeContinuation(_ continuation: AsyncStream<Element>.Continuation) {
53+
tasks.append(continuation)
54+
}
55+
56+
nonisolated
57+
public func finish() {
58+
Task { await _finish() }
59+
}
60+
61+
func _finish() {
62+
let tasks = self.tasks
63+
self.tasks = []
64+
for task in tasks {
65+
task.finish()
66+
}
67+
}
68+
}
69+

Tool/Sources/XcodeInspector/Apps/XcodeAppInstanceInspector.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import AppKit
2-
import AsyncExtensions
2+
import AsyncPassthroughSubject
33
import AXExtension
44
import AXNotificationStream
55
import Combine
@@ -123,7 +123,7 @@ public final class XcodeAppInstanceInspector: AppInstanceInspector {
123123
private var focusedWindowObservations = Set<AnyCancellable>()
124124

125125
deinit {
126-
axNotifications.send(.finished)
126+
axNotifications.finish()
127127
for task in longRunningTasks { task.cancel() }
128128
}
129129

Tool/Sources/XcodeInspector/SourceEditor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import AppKit
2-
import AsyncExtensions
2+
import AsyncPassthroughSubject
33
import AXNotificationStream
44
import Foundation
55
import Logger

Tool/Sources/XcodeInspector/XcodeInspector.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ public final class XcodeInspector: ObservableObject {
286286

287287
setFocusedElement()
288288
let focusedElementChanged = Task { @XcodeInspectorActor in
289-
for await notification in xcode.axNotifications {
289+
for await notification in await xcode.axNotifications.notifications() {
290290
if notification.kind == .focusedUIElementChanged {
291291
try Task.checkCancellation()
292292
setFocusedElement()
@@ -301,7 +301,7 @@ public final class XcodeInspector: ObservableObject {
301301
{
302302
let malfunctionCheck = Task { @XcodeInspectorActor [weak self] in
303303
if #available(macOS 13.0, *) {
304-
let notifications = xcode.axNotifications.filter {
304+
let notifications = await xcode.axNotifications.notifications().filter {
305305
$0.kind == .uiElementDestroyed
306306
}.debounce(for: .milliseconds(1000))
307307
for await _ in notifications {

Tool/Sources/XcodeInspector/XcodeWindowInspector.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import AppKit
2-
import AsyncExtensions
2+
import AsyncPassthroughSubject
33
import AXExtension
44
import Combine
55
import Foundation
@@ -19,11 +19,6 @@ public final class WorkspaceXcodeWindowInspector: XcodeWindowInspector {
1919
@Published var workspaceURL: URL = .init(fileURLWithPath: "/")
2020
@Published var projectRootURL: URL = .init(fileURLWithPath: "/")
2121
private var focusedElementChangedTask: Task<Void, Error>?
22-
let axNotifications: AsyncPassthroughSubject<XcodeAppInstanceInspector.AXNotification>
23-
24-
deinit {
25-
focusedElementChangedTask?.cancel()
26-
}
2722

2823
public func refresh() {
2924
Task { @XcodeInspectorActor in updateURLs() }
@@ -35,7 +30,6 @@ public final class WorkspaceXcodeWindowInspector: XcodeWindowInspector {
3530
axNotifications: AsyncPassthroughSubject<XcodeAppInstanceInspector.AXNotification>
3631
) {
3732
self.app = app
38-
self.axNotifications = axNotifications
3933
super.init(uiElement: uiElement)
4034

4135
focusedElementChangedTask = Task { [weak self, axNotifications] in
@@ -51,7 +45,7 @@ public final class WorkspaceXcodeWindowInspector: XcodeWindowInspector {
5145
}
5246

5347
group.addTask { [weak self] in
54-
for await notification in axNotifications {
48+
for await notification in await axNotifications.notifications() {
5549
guard notification.kind == .focusedUIElementChanged else { continue }
5650
guard let self else { return }
5751
try Task.checkCancellation()

0 commit comments

Comments
 (0)