Skip to content

Commit cc4ff83

Browse files
committed
Tweak real-time suggestion trigger
1 parent e550180 commit cc4ff83

1 file changed

Lines changed: 35 additions & 20 deletions

File tree

Core/Sources/Service/RealtimeSuggestionController.swift

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

Comments
 (0)