forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXcodeWindowInspector.swift
More file actions
107 lines (94 loc) · 3.45 KB
/
XcodeWindowInspector.swift
File metadata and controls
107 lines (94 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import AppKit
import AXExtension
import AXNotificationStream
import Combine
import Foundation
public class XcodeWindowInspector: ObservableObject {
let uiElement: AXUIElement
init(uiElement: AXUIElement) {
self.uiElement = uiElement
}
}
public final class WorkspaceXcodeWindowInspector: XcodeWindowInspector {
let app: NSRunningApplication
@Published var documentURL: URL = .init(fileURLWithPath: "/")
@Published var projectURL: URL = .init(fileURLWithPath: "/")
private var updateTabsTask: Task<Void, Error>?
private var focusedElementChangedTask: Task<Void, Error>?
deinit {
updateTabsTask?.cancel()
focusedElementChangedTask?.cancel()
}
public init(app: NSRunningApplication, uiElement: AXUIElement) {
self.app = app
super.init(uiElement: uiElement)
focusedElementChangedTask = Task { @MainActor in
let update = {
let documentURL = Self.extractDocumentURL(windowElement: uiElement)
if let documentURL {
self.documentURL = documentURL
}
let projectURL = Self.extractProjectURL(
windowElement: uiElement,
fileURL: documentURL
)
if let projectURL {
self.projectURL = projectURL
}
}
update()
let notifications = AXNotificationStream(
app: app,
notificationNames: kAXFocusedUIElementChangedNotification
)
for await _ in notifications {
try Task.checkCancellation()
update()
}
}
}
static func extractDocumentURL(
windowElement: AXUIElement
) -> URL? {
// fetch file path of the frontmost window of Xcode through Accessibility API.
let path = windowElement.document
if let path = path?.removingPercentEncoding {
let url = URL(
fileURLWithPath: path
.replacingOccurrences(of: "file://", with: "")
)
return url
}
return nil
}
static func extractProjectURL(
windowElement: AXUIElement,
fileURL: URL?
) -> URL? {
for child in windowElement.children {
if child.description.starts(with: "/"), child.description.count > 1 {
let path = child.description
let trimmedNewLine = path.trimmingCharacters(in: .newlines)
var url = URL(fileURLWithPath: trimmedNewLine)
while !FileManager.default.fileIsDirectory(atPath: url.path) ||
!url.pathExtension.isEmpty
{
url = url.deletingLastPathComponent()
}
return url
}
}
guard var currentURL = fileURL else { return nil }
var firstDirectoryURL: URL?
while currentURL.pathComponents.count > 1 {
defer { currentURL.deleteLastPathComponent() }
guard FileManager.default.fileIsDirectory(atPath: currentURL.path) else { continue }
if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
let gitURL = currentURL.appendingPathComponent(".git")
if FileManager.default.fileIsDirectory(atPath: gitURL.path) {
return currentURL
}
}
return firstDirectoryURL ?? fileURL
}
}