@@ -2,7 +2,7 @@ import ActiveApplicationMonitor
22import AppKit
33import AsyncAlgorithms
44import AXExtension
5- import AXNotificationStream
5+ import Combine
66import Foundation
77import Logger
88import Preferences
@@ -11,21 +11,16 @@ import Workspace
1111import XcodeInspector
1212
1313public actor RealtimeSuggestionController {
14- private var task : Task < Void , Error > ?
14+ private var cancellable : Set < AnyCancellable > = [ ]
1515 private var inflightPrefetchTask : Task < Void , Error > ?
16- private var windowChangeObservationTask : Task < Void , Error > ?
17- private var activeApplicationMonitorTask : Task < Void , Error > ?
1816 private var editorObservationTask : Task < Void , Error > ?
19- private var focusedUIElement : AXUIElement ?
2017 private var sourceEditor : SourceEditor ?
2118
2219 init ( ) { }
2320
2421 deinit {
25- task ? . cancel ( )
22+ cancellable . forEach { $0 . cancel ( ) }
2623 inflightPrefetchTask? . cancel ( )
27- windowChangeObservationTask? . cancel ( )
28- activeApplicationMonitorTask? . cancel ( )
2924 editorObservationTask? . cancel ( )
3025 }
3126
@@ -35,80 +30,35 @@ public actor RealtimeSuggestionController {
3530 }
3631
3732 private func observeXcodeChange( ) {
38- task? . cancel ( )
39- task = Task { [ weak self] in
40- if ActiveApplicationMonitor . shared. activeXcode != nil {
41- await self ? . handleXcodeChanged ( )
42- }
43- var previousApp = ActiveApplicationMonitor . shared. activeXcode? . info
44- for await app in ActiveApplicationMonitor . shared. createInfoStream ( ) {
33+ cancellable. forEach { $0. cancel ( ) }
34+
35+ XcodeInspector . shared. $focusedEditor
36+ . sink { [ weak self] editor in
4537 guard let self else { return }
46- try Task . checkCancellation ( )
47- defer { previousApp = app }
48-
49- if let app = ActiveApplicationMonitor . shared. activeXcode,
50- app. processIdentifier != previousApp? . processIdentifier
51- {
52- await self . handleXcodeChanged ( )
38+ Task {
39+ guard let editor else { return }
40+ await self . handleFocusElementChange ( editor)
5341 }
54- }
55- }
56- }
57-
58- private func handleXcodeChanged( ) {
59- guard let app = ActiveApplicationMonitor . shared. activeXcode else { return }
60- windowChangeObservationTask? . cancel ( )
61- windowChangeObservationTask = nil
62- observeXcodeWindowChangeIfNeeded ( app)
63- }
64-
65- private func observeXcodeWindowChangeIfNeeded( _ app: NSRunningApplication ) {
66- guard windowChangeObservationTask == nil else { return }
67- handleFocusElementChange ( )
68-
69- let notifications = AXNotificationStream (
70- app: app,
71- notificationNames: kAXFocusedUIElementChangedNotification,
72- kAXMainWindowChangedNotification
73- )
74- windowChangeObservationTask = Task { [ weak self] in
75- for await _ in notifications {
76- guard let self else { return }
77- try Task . checkCancellation ( )
78- await self . handleFocusElementChange ( )
79- }
80- }
42+ } . store ( in: & cancellable)
8143 }
8244
83- private func handleFocusElementChange( ) {
84- guard let activeXcode = ActiveApplicationMonitor . shared. activeXcode else { return }
85- let application = AXUIElementCreateApplication ( activeXcode. processIdentifier)
86- guard let focusElement = application. focusedElement else { return }
87- let focusElementType = focusElement. description
88- focusedUIElement = focusElement
89-
45+ private func handleFocusElementChange( _ sourceEditor: SourceEditor ) {
9046 Task { // Notify suggestion service for open file.
9147 try await Task . sleep ( nanoseconds: 500_000_000 )
9248 guard let fileURL = XcodeInspector . shared. realtimeActiveDocumentURL else { return }
9349 _ = try await Service . shared. workspacePool
9450 . fetchOrCreateWorkspaceAndFilespace ( fileURL: fileURL)
9551 }
9652
97- guard focusElementType == " Source Editor " else { return }
98- sourceEditor = SourceEditor ( runningApplication: activeXcode, element: focusElement)
53+ self . sourceEditor = sourceEditor
54+
55+ let notificationsFromEditor = sourceEditor. axNotifications
9956
10057 editorObservationTask? . cancel ( )
10158 editorObservationTask = nil
10259
103- let notificationsFromEditor = AXNotificationStream (
104- app: activeXcode,
105- element: focusElement,
106- notificationNames: kAXValueChangedNotification, kAXSelectedTextChangedNotification
107- )
108-
10960 editorObservationTask = Task { [ weak self] in
110- guard let fileURL = XcodeInspector . shared. realtimeActiveDocumentURL else { return }
111- if let sourceEditor = await self ? . sourceEditor {
61+ if let fileURL = XcodeInspector . shared. realtimeActiveDocumentURL {
11262 await PseudoCommandHandler ( ) . invalidateRealtimeSuggestionsIfNeeded (
11363 fileURL: fileURL,
11464 sourceEditor: sourceEditor
@@ -119,21 +69,22 @@ public actor RealtimeSuggestionController {
11969 guard let self else { return }
12070 try Task . checkCancellation ( )
12171
122- switch notification. name {
123- case kAXValueChangedNotification:
72+ switch notification. kind {
73+ case . valueChanged:
74+ Logger . service. debug ( " Receive valueChanged from editor " )
12475 await cancelInFlightTasks ( )
12576 await self . triggerPrefetchDebounced ( )
126- await self . notifyEditingFileChange ( editor: focusElement )
127- case kAXSelectedTextChangedNotification :
128- guard let sourceEditor = await sourceEditor ,
129- let fileURL = XcodeInspector . shared. activeDocumentURL
130- else { continue }
77+ await self . notifyEditingFileChange ( editor: sourceEditor . element )
78+ case . selectedTextChanged :
79+ Logger . service . debug ( " Receive selectedTextChanged from editor " )
80+ guard let fileURL = XcodeInspector . shared. activeDocumentURL
81+ else { break }
13182 await PseudoCommandHandler ( ) . invalidateRealtimeSuggestionsIfNeeded (
13283 fileURL: fileURL,
13384 sourceEditor: sourceEditor
13485 )
13586 default :
136- continue
87+ break
13788 }
13889 }
13990 }
@@ -179,8 +130,6 @@ public actor RealtimeSuggestionController {
179130 }
180131 if Task. isCancelled { return }
181132
182- // Logger.service.info("Prefetch suggestions.")
183-
184133 // So the editor won't be blocked (after information are cached)!
185134 await PseudoCommandHandler ( ) . generateRealtimeSuggestions ( sourceEditor: sourceEditor)
186135 }
0 commit comments