Skip to content

Commit 45e5a30

Browse files
committed
Merge branch 'feature/accept-suggestion-via-accessibility-api' into develop
2 parents 86d309c + 5714f64 commit 45e5a30

6 files changed

Lines changed: 99 additions & 8 deletions

File tree

Copilot for Xcode/SettingsView.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ final class Settings: ObservableObject {
1515
var suggestionWidgetPositionMode: SuggestionWidgetPositionMode
1616
@AppStorage(\.widgetColorScheme)
1717
var widgetColorScheme: WidgetColorScheme
18+
@AppStorage(\.acceptSuggestionWithAccessibilityAPI)
19+
var acceptSuggestionWithAccessibilityAPI: Bool
1820
init() {}
1921
}
2022

@@ -107,6 +109,11 @@ struct SettingsView: View {
107109
.fill(Color.white.opacity(0.2))
108110
)
109111
}
112+
113+
Toggle(isOn: $settings.acceptSuggestionWithAccessibilityAPI) {
114+
Text("Use accessibility API to accept suggestion in widget")
115+
}
116+
.toggleStyle(.switch)
110117
}
111118
}.buttonStyle(.copilot)
112119
}

Core/Sources/Preferences/Keys.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ public struct UserDefaultPreferenceKeys {
100100

101101
public var chatGPTLanguage: ChatGPTLanguage { .init() }
102102

103+
public struct AcceptSuggestionWithAccessibilityAPI: UserDefaultPreferenceKey {
104+
public let defaultValue = false
105+
public let key = "AcceptSuggestionWithAccessibilityAPI"
106+
}
107+
108+
public var acceptSuggestionWithAccessibilityAPI: AcceptSuggestionWithAccessibilityAPI {
109+
.init()
110+
}
111+
103112
public var disableLazyVStack: FeatureFlags.DisableLazyVStack { .init() }
104113
}
105114

Core/Sources/Service/GUI/GraphicalUserInterfaceController.swift.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ public final class GraphicalUserInterfaceController {
3131
}
3232

3333
suggestionWidget.onAcceptButtonTapped = {
34-
Task {
35-
try await Environment.triggerAction("Accept Suggestion")
34+
Task { @ServiceActor in
35+
let handler = PseudoCommandHandler()
36+
await handler.acceptSuggestion()
3637
}
3738
}
3839
}

Core/Sources/Service/SuggestionCommandHandler/PseudoCommandHandler.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,71 @@ struct PseudoCommandHandler {
6868
usesTabsForIndentation: false
6969
))
7070
}
71+
72+
func acceptSuggestion() async {
73+
if UserDefaults.shared.value(for: \.acceptSuggestionWithAccessibilityAPI) {
74+
guard let xcode = ActiveApplicationMonitor.activeXcode else { return }
75+
let application = AXUIElementCreateApplication(xcode.processIdentifier)
76+
guard let focusElement = application.focusedElement,
77+
focusElement.description == "Source Editor"
78+
else { return }
79+
guard let (content, lines, cursorPosition) = await getFileContent() else {
80+
PresentInWindowSuggestionPresenter()
81+
.presentErrorMessage("Unable to get file content.")
82+
return
83+
}
84+
let handler = CommentBaseCommandHandler()
85+
do {
86+
guard let result = try await handler.acceptSuggestion(editor: .init(
87+
content: content,
88+
lines: lines,
89+
uti: "",
90+
cursorPosition: cursorPosition,
91+
selections: [],
92+
tabSize: 0,
93+
indentSize: 0,
94+
usesTabsForIndentation: false
95+
)) else { return }
96+
97+
let oldPosition = focusElement.selectedTextRange
98+
99+
let error = AXUIElementSetAttributeValue(
100+
focusElement,
101+
kAXValueAttribute as CFString,
102+
result.content as CFTypeRef
103+
)
104+
105+
if error != AXError.success {
106+
PresentInWindowSuggestionPresenter()
107+
.presentErrorMessage("Fail to set editor content.")
108+
}
109+
110+
if let oldPosition {
111+
var range = CFRange(
112+
location: oldPosition.lowerBound,
113+
length: 0
114+
)
115+
if let value = AXValueCreate(.cfRange, &range) {
116+
AXUIElementSetAttributeValue(
117+
focusElement,
118+
kAXSelectedTextRangeAttribute as CFString,
119+
value
120+
)
121+
}
122+
}
123+
124+
} catch {
125+
PresentInWindowSuggestionPresenter().presentError(error)
126+
}
127+
} else {
128+
do {
129+
try await Environment.triggerAction("Accept Suggestion")
130+
return
131+
} catch {
132+
PresentInWindowSuggestionPresenter().presentError(error)
133+
}
134+
}
135+
}
71136
}
72137

73138
private extension PseudoCommandHandler {

Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ struct PresentInWindowSuggestionPresenter {
4747
}
4848
}
4949

50+
func presentErrorMessage(_ message: String) {
51+
Task { @MainActor in
52+
let controller = GraphicalUserInterfaceController.shared.suggestionWidget
53+
controller.presentError(message)
54+
}
55+
}
56+
5057
func closeChatRoom(fileURL: URL) {
5158
Task { @MainActor in
5259
let controller = GraphicalUserInterfaceController.shared.suggestionWidget

Core/Sources/Service/Workspace.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,12 @@ extension Workspace {
118118
filespaces[fileURL] = filespace
119119
}
120120

121-
filespace.uti = editor.uti
122-
filespace.tabSize = editor.tabSize
123-
filespace.indentSize = editor.indentSize
124-
filespace.usesTabsForIndentation = editor.usesTabsForIndentation
121+
if !editor.uti.isEmpty {
122+
filespace.uti = editor.uti
123+
filespace.tabSize = editor.tabSize
124+
filespace.indentSize = editor.indentSize
125+
filespace.usesTabsForIndentation = editor.usesTabsForIndentation
126+
}
125127

126128
let snapshot = Filespace.Snapshot(
127129
linesHash: editor.lines.hashValue,
@@ -174,7 +176,7 @@ extension Workspace {
174176
cancelInFlightRealtimeSuggestionRequests()
175177
lastTriggerDate = Environment.now()
176178

177-
if let editor {
179+
if let editor, !editor.uti.isEmpty {
178180
filespaces[fileURL]?.uti = editor.uti
179181
filespaces[fileURL]?.tabSize = editor.tabSize
180182
filespaces[fileURL]?.indentSize = editor.indentSize
@@ -195,7 +197,7 @@ extension Workspace {
195197
filespace.suggestionIndex < filespace.suggestions.endIndex
196198
else { return nil }
197199

198-
if let editor {
200+
if let editor, !editor.uti.isEmpty {
199201
filespaces[fileURL]?.uti = editor.uti
200202
filespaces[fileURL]?.tabSize = editor.tabSize
201203
filespaces[fileURL]?.indentSize = editor.indentSize

0 commit comments

Comments
 (0)