@@ -6,6 +6,7 @@ import Environment
66import QuartzCore
77import SwiftUI
88import XPCShared
9+ import AsyncAlgorithms
910
1011/// Present a tiny dot next to mouse cursor if real-time suggestion is enabled.
1112@MainActor
@@ -94,6 +95,7 @@ final class RealtimeSuggestionIndicatorController {
9495 private var userDefaultsObserver = UserDefaultsObserver ( )
9596 private var windowChangeObservationTask : Task < Void , Error > ?
9697 private var activeApplicationMonitorTask : Task < Void , Error > ?
98+ private var editorObservationTask : Task < Void , Error > ?
9799 private var xcode : NSRunningApplication ?
98100 var isObserving = false {
99101 didSet {
@@ -124,6 +126,7 @@ final class RealtimeSuggestionIndicatorController {
124126
125127 nonisolated init ( ) {
126128 Task { @MainActor in
129+ observeEditorChangeIfNeeded ( )
127130 activeApplicationMonitorTask = Task { [ weak self] in
128131 var previousApp : NSRunningApplication ?
129132 for await app in ActiveApplicationMonitor . createStream ( ) {
@@ -170,14 +173,61 @@ final class RealtimeSuggestionIndicatorController {
170173 notificationNames:
171174 kAXMovedNotification,
172175 kAXResizedNotification,
173- kAXMainWindowChangedNotification,
174176 kAXFocusedWindowChangedNotification,
175- kAXFocusedUIElementChangedNotification,
176- kAXSelectedTextChangedNotification
177+ kAXFocusedUIElementChangedNotification
177178 )
178- for await _ in notifications {
179+ for await notification in notifications {
179180 try Task . checkCancellation ( )
180181 updateIndicatorLocation ( )
182+
183+ switch notification. name {
184+ case kAXFocusedUIElementChangedNotification, kAXFocusedWindowChangedNotification:
185+ editorObservationTask? . cancel ( )
186+ editorObservationTask = nil
187+ observeEditorChangeIfNeeded ( )
188+ default :
189+ continue
190+ }
191+ }
192+ }
193+ }
194+
195+ private func observeEditorChangeIfNeeded( ) {
196+ guard editorObservationTask == nil ,
197+ let activeXcode = ActiveApplicationMonitor . activeXcode
198+ else { return }
199+ let application = AXUIElementCreateApplication ( activeXcode. processIdentifier)
200+ guard let focusElement: AXUIElement = try ? application
201+ . copyValue ( key: kAXFocusedUIElementAttribute) ,
202+ let focusElementType: String = try ? focusElement
203+ . copyValue ( key: kAXDescriptionAttribute) ,
204+ focusElementType == " Source Editor " ,
205+ let scrollView: AXUIElement = try ? focusElement
206+ . copyValue ( key: kAXParentAttribute) ,
207+ let scrollBar: AXUIElement = try ? scrollView
208+ . copyValue ( key: kAXVerticalScrollBarAttribute)
209+ else { return }
210+
211+ editorObservationTask = Task { [ weak self] in
212+ let notificationsFromEditor = AXNotificationStream (
213+ app: activeXcode,
214+ element: focusElement,
215+ notificationNames:
216+ kAXResizedNotification,
217+ kAXMovedNotification,
218+ kAXLayoutChangedNotification,
219+ kAXSelectedTextChangedNotification
220+ )
221+
222+ let notificationsFromScrollBar = AXNotificationStream (
223+ app: activeXcode,
224+ element: scrollBar,
225+ notificationNames: kAXValueChangedNotification
226+ )
227+
228+ for await _ in merge ( notificationsFromEditor, notificationsFromScrollBar) {
229+ try Task . checkCancellation ( )
230+ self ? . updateIndicatorLocation ( )
181231 }
182232 }
183233 }
@@ -198,10 +248,7 @@ final class RealtimeSuggestionIndicatorController {
198248 return
199249 }
200250
201- if let activeXcode = NSRunningApplication
202- . runningApplications ( withBundleIdentifier: " com.apple.dt.Xcode " )
203- . first ( where: \. isActive)
204- {
251+ if let activeXcode = ActiveApplicationMonitor . activeXcode {
205252 let application = AXUIElementCreateApplication ( activeXcode. processIdentifier)
206253 if let focusElement: AXUIElement = try ? application
207254 . copyValue ( key: kAXFocusedUIElementAttribute) ,
0 commit comments