@@ -41,8 +41,10 @@ public class RealtimeSuggestionController {
4141 if let app = ActiveApplicationMonitor . activeXcode, app != previousApp {
4242 await self . handleXcodeChanged ( app)
4343 }
44-
45- #warning("TOOD: Is it possible to get rid of hid event observation with only AXObserver?")
44+
45+ #warning(
46+ " TOOD: Is it possible to get rid of hid event observation with only AXObserver? "
47+ )
4648 if ActiveApplicationMonitor . activeXcode != nil {
4749 await startHIDObservation ( by: 1 )
4850 } else {
@@ -67,7 +69,6 @@ public class RealtimeSuggestionController {
6769
6870 private func stopHIDObservation( by listener: AnyHashable ) {
6971 os_log ( . info, " Remove auto trigger listener: %@. " , listener as CVarArg )
70- os_log ( . info, " Auto trigger is stopped. " )
7172 task? . cancel ( )
7273 task = nil
7374 eventObserver. deactivate ( )
@@ -97,13 +98,15 @@ public class RealtimeSuggestionController {
9798 }
9899
99100 private func handleFocusElementChange( ) {
100- editorObservationTask? . cancel ( )
101- editorObservationTask = nil
102101 guard let activeXcode = ActiveApplicationMonitor . activeXcode else { return }
103102 let application = AXUIElementCreateApplication ( activeXcode. processIdentifier)
104103 guard let focusElement = application. focusedElement else { return }
105104 let focusElementType = focusElement. description
106105 guard focusElementType == " Source Editor " else { return }
106+ focusedUIElement = focusElement
107+
108+ editorObservationTask? . cancel ( )
109+ editorObservationTask = nil
107110
108111 editorObservationTask = Task { [ weak self] in
109112 let notificationsFromEditor = AXNotificationStream (
@@ -130,18 +133,17 @@ public class RealtimeSuggestionController {
130133 func handleHIDEvent( event: CGEvent ) async {
131134 guard await Environment . isXcodeActive ( ) else { return }
132135
133- let keycode = Int ( event. getIntegerValueField ( . keyboardEventKeycode) )
134-
135- let escape = 0x35
136- let arrowKeys = [ 0x7B , 0x7C , 0x7D , 0x7E ]
137-
138136 // Mouse clicks should cancel in-flight tasks.
139137 if [ CGEventType . rightMouseDown, . leftMouseDown] . contains ( event. type) {
140138 await cancelInFlightTasks ( )
141139 return
142140 }
143141
144- // Arror keys should cancel in-flight tasks.
142+ let keycode = Int ( event. getIntegerValueField ( . keyboardEventKeycode) )
143+ let escape = 0x35
144+ let arrowKeys = [ 0x7B , 0x7C , 0x7D , 0x7E ]
145+
146+ // Arrow keys should cancel in-flight tasks.
145147 if arrowKeys. contains ( keycode) {
146148 await cancelInFlightTasks ( )
147149 return
@@ -150,8 +152,20 @@ public class RealtimeSuggestionController {
150152 // Escape should cancel in-flight tasks.
151153 // Except that when the completion panel is presented, it should trigger prefetch instead.
152154 if keycode == escape {
153- await cancelInFlightTasks ( )
154- if isCompletionPanelPresenting ( ) { triggerPrefetchDebounced ( force: true ) }
155+ if event. type == . keyDown {
156+ await cancelInFlightTasks ( )
157+ } else {
158+ let task = Task {
159+ #warning(
160+ " TODO: Any method to avoid using AppleScript to check that completion panel is presented? "
161+ )
162+ if await Environment . frontmostXcodeWindowIsEditor ( ) {
163+ if Task . isCancelled { return }
164+ self . triggerPrefetchDebounced ( force: true )
165+ }
166+ }
167+ inflightRealtimeSuggestionsTasks. insert ( task)
168+ }
155169 }
156170 }
157171
@@ -177,7 +191,6 @@ public class RealtimeSuggestionController {
177191
178192 do {
179193 try await Environment . triggerAction ( " Prefetch Suggestions " )
180-
181194 } catch {
182195 os_log ( . info, " %@ " , error. localizedDescription)
183196 }
@@ -209,15 +222,13 @@ public class RealtimeSuggestionController {
209222 }
210223 }
211224
225+ /// This method will still return true if the completion panel is hidden by esc.
226+ /// Looks like the Xcode will keep the panel around until content is changed,
227+ /// not sure how to observe that it's hidden.
212228 func isCompletionPanelPresenting( ) -> Bool {
213229 guard let activeXcode = ActiveApplicationMonitor . activeXcode else { return false }
214230 let application = AXUIElementCreateApplication ( activeXcode. processIdentifier)
215- if let completionPanel = application. child ( identifier: " _XC_COMPLETION_TABLE_ " ) ,
216- completionPanel. window != nil
217- {
218- return true
219- }
220- return false
231+ return application. focusedWindow? . child ( identifier: " _XC_COMPLETION_TABLE_ " ) != nil
221232 }
222233}
223234
@@ -242,6 +253,10 @@ extension AXUIElement {
242253 try ? copyValue ( key: kAXWindowAttribute)
243254 }
244255
256+ var focusedWindow : AXUIElement ? {
257+ try ? copyValue ( key: kAXFocusedWindowAttribute)
258+ }
259+
245260 var topLevelElement : AXUIElement ? {
246261 try ? copyValue ( key: kAXTopLevelUIElementAttribute)
247262 }
0 commit comments