Skip to content

Commit 3681e3c

Browse files
committed
Use Accessibility API for project root url fetch
1 parent 5d16a49 commit 3681e3c

File tree

1 file changed

+29
-73
lines changed

1 file changed

+29
-73
lines changed

Core/Sources/Environment/Environment.swift

Lines changed: 29 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ActiveApplicationMonitor
22
import AppKit
3+
import AXExtension
34
import CopilotService
45
import Foundation
56

@@ -40,28 +41,24 @@ public enum Environment {
4041
}
4142
}
4243

43-
#warning("""
44-
TODO: The current version causes an issue in real-time suggestion that when completion panel is open,
45-
the command handler can not find the correct workspace.
46-
""")
4744
public static var fetchCurrentProjectRootURL: (_ fileURL: URL?) async throws
4845
-> URL? = { fileURL in
49-
let appleScript = """
50-
tell application "Xcode"
51-
return path of document of the first window
52-
end tell
53-
"""
54-
55-
let path = (try? await runAppleScript(appleScript)) ?? ""
56-
if !path.isEmpty {
57-
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
58-
var url = URL(fileURLWithPath: trimmedNewLine)
59-
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
60-
!url.pathExtension.isEmpty
61-
{
62-
url = url.deletingLastPathComponent()
46+
if let xcode = ActiveApplicationMonitor.activeXcode {
47+
let application = AXUIElementCreateApplication(xcode.processIdentifier)
48+
let focusedWindow = application.focusedWindow
49+
for child in focusedWindow?.children ?? [] {
50+
if child.description.starts(with: "/") {
51+
let path = child.description
52+
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
53+
var url = URL(fileURLWithPath: trimmedNewLine)
54+
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
55+
!url.pathExtension.isEmpty
56+
{
57+
url = url.deletingLastPathComponent()
58+
}
59+
return url
60+
}
6361
}
64-
return url
6562
}
6663

6764
guard var currentURL = fileURL else { return nil }
@@ -86,31 +83,21 @@ public enum Environment {
8683

8784
// fetch file path of the frontmost window of Xcode through Accessability API.
8885
let application = AXUIElementCreateApplication(xcode.processIdentifier)
89-
do {
90-
let frontmostWindow: AXUIElement = try application
91-
.copyValue(key: kAXFocusedWindowAttribute)
92-
var path: String? = try? frontmostWindow.copyValue(key: kAXDocumentAttribute)
93-
if path == nil {
94-
for window in try application.copyValue(
95-
key: kAXWindowsAttribute,
96-
ofType: [AXUIElement].self
97-
) {
98-
path = try? window.copyValue(key: kAXDocumentAttribute)
99-
if path != nil { break }
100-
}
101-
}
102-
if let path = path?.removingPercentEncoding {
103-
let url = URL(
104-
fileURLWithPath: path
105-
.replacingOccurrences(of: "file://", with: "")
106-
)
107-
return url
108-
}
109-
} catch {
110-
if let axError = error as? AXError, axError == .apiDisabled {
111-
throw NoAccessToAccessibilityAPIError()
86+
let focusedWindow = application.focusedWindow
87+
var path = focusedWindow?.document
88+
if path == nil {
89+
for window in application.windows {
90+
path = window.document
91+
if path != nil { break }
11292
}
11393
}
94+
if let path = path?.removingPercentEncoding {
95+
let url = URL(
96+
fileURLWithPath: path
97+
.replacingOccurrences(of: "file://", with: "")
98+
)
99+
return url
100+
}
114101
throw FailedToFetchFileURLError()
115102
}
116103

@@ -158,37 +145,6 @@ public enum Environment {
158145
}
159146
}
160147

161-
extension AXError: Error {}
162-
163-
public extension AXUIElement {
164-
func copyValue<T>(key: String, ofType _: T.Type = T.self) throws -> T {
165-
var value: AnyObject?
166-
let error = AXUIElementCopyAttributeValue(self, key as CFString, &value)
167-
if error == .success, let value = value as? T {
168-
return value
169-
}
170-
throw error
171-
}
172-
173-
func copyParameterizedValue<T>(
174-
key: String,
175-
parameters: AnyObject,
176-
ofType _: T.Type = T.self
177-
) throws -> T {
178-
var value: AnyObject?
179-
let error = AXUIElementCopyParameterizedAttributeValue(
180-
self,
181-
key as CFString,
182-
parameters as CFTypeRef,
183-
&value
184-
)
185-
if error == .success, let value = value as? T {
186-
return value
187-
}
188-
throw error
189-
}
190-
}
191-
192148
@discardableResult
193149
func runAppleScript(_ appleScript: String) async throws -> String {
194150
let task = Process()

0 commit comments

Comments
 (0)