@@ -3,26 +3,28 @@ import AppKit
33import CopilotService
44import Foundation
55
6- struct NoAccessToAccessibilityAPIError : Error , LocalizedError {
7- var errorDescription : String ? {
6+ public struct NoAccessToAccessibilityAPIError : Error , LocalizedError {
7+ public var errorDescription : String ? {
88 " Accessibility API permission is not granted. Please enable in System Settings.app. "
99 }
10+ public init ( ) { }
1011}
1112
12- struct FailedToFetchFileURLError : Error , LocalizedError {
13- var errorDescription : String ? {
13+ public struct FailedToFetchFileURLError : Error , LocalizedError {
14+ public var errorDescription : String ? {
1415 " Failed to fetch editing file url. "
1516 }
17+ public init ( ) { }
1618}
1719
18- enum Environment {
19- static var now = { Date ( ) }
20+ public enum Environment {
21+ public static var now = { Date ( ) }
2022
21- static var isXcodeActive : ( ) async -> Bool = {
23+ public static var isXcodeActive : ( ) async -> Bool = {
2224 ActiveApplicationMonitor . activeXcode != nil
2325 }
2426
25- static var frontmostXcodeWindowIsEditor : ( ) async -> Bool = {
27+ public static var frontmostXcodeWindowIsEditor : ( ) async -> Bool = {
2628 let appleScript = """
2729 tell application " Xcode "
2830 return path of document of the first window
@@ -36,41 +38,42 @@ enum Environment {
3638 }
3739 }
3840
39- static var fetchCurrentProjectRootURL : ( _ fileURL: URL ? ) async throws -> URL ? = { fileURL in
40- let appleScript = """
41- tell application " Xcode "
42- return path of document of the first window
43- end tell
44- """
45-
46- let path = ( try ? await runAppleScript ( appleScript) ) ?? " "
47- if !path. isEmpty {
48- let trimmedNewLine = path. trimmingCharacters ( in: . newlines)
49- var url = URL ( fileURLWithPath: trimmedNewLine)
50- while !FileManager. default. fileIsDirectory ( atPath: url. path) ||
51- !url. pathExtension. isEmpty
52- {
53- url = url. deletingLastPathComponent ( )
41+ public static var fetchCurrentProjectRootURL : ( _ fileURL: URL ? ) async throws
42+ -> URL ? = { fileURL in
43+ let appleScript = """
44+ tell application " Xcode "
45+ return path of document of the first window
46+ end tell
47+ """
48+
49+ let path = ( try ? await runAppleScript ( appleScript) ) ?? " "
50+ if !path. isEmpty {
51+ let trimmedNewLine = path. trimmingCharacters ( in: . newlines)
52+ var url = URL ( fileURLWithPath: trimmedNewLine)
53+ while !FileManager. default. fileIsDirectory ( atPath: url. path) ||
54+ !url. pathExtension. isEmpty
55+ {
56+ url = url. deletingLastPathComponent ( )
57+ }
58+ return url
5459 }
55- return url
56- }
5760
58- guard var currentURL = fileURL else { return nil }
59- var firstDirectoryURL : URL ?
60- while currentURL. pathComponents. count > 1 {
61- defer { currentURL. deleteLastPathComponent ( ) }
62- guard FileManager . default. fileIsDirectory ( atPath: currentURL. path) else { continue }
63- if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
64- let gitURL = currentURL. appendingPathComponent ( " .git " )
65- if FileManager . default. fileIsDirectory ( atPath: gitURL. path) {
66- return currentURL
61+ guard var currentURL = fileURL else { return nil }
62+ var firstDirectoryURL : URL ?
63+ while currentURL. pathComponents. count > 1 {
64+ defer { currentURL. deleteLastPathComponent ( ) }
65+ guard FileManager . default. fileIsDirectory ( atPath: currentURL. path) else { continue }
66+ if firstDirectoryURL == nil { firstDirectoryURL = currentURL }
67+ let gitURL = currentURL. appendingPathComponent ( " .git " )
68+ if FileManager . default. fileIsDirectory ( atPath: gitURL. path) {
69+ return currentURL
70+ }
6771 }
68- }
6972
70- return firstDirectoryURL ?? fileURL
71- }
73+ return firstDirectoryURL ?? fileURL
74+ }
7275
73- static var fetchCurrentFileURL : ( ) async throws -> URL = {
76+ public static var fetchCurrentFileURL : ( ) async throws -> URL = {
7477 guard let xcode = ActiveApplicationMonitor . activeXcode else {
7578 throw FailedToFetchFileURLError ( )
7679 }
@@ -105,16 +108,16 @@ enum Environment {
105108 throw FailedToFetchFileURLError ( )
106109 }
107110
108- static var createAuthService : ( ) -> CopilotAuthServiceType = {
111+ public static var createAuthService : ( ) -> CopilotAuthServiceType = {
109112 CopilotAuthService ( )
110113 }
111114
112- static var createSuggestionService : ( _ projectRootURL: URL )
115+ public static var createSuggestionService : ( _ projectRootURL: URL )
113116 -> CopilotSuggestionServiceType = { projectRootURL in
114117 CopilotSuggestionService ( projectRootURL: projectRootURL)
115118 }
116119
117- static var triggerAction : ( _ name: String ) async throws -> Void = { name in
120+ public static var triggerAction : ( _ name: String ) async throws -> Void = { name in
118121 var xcodes = [ NSRunningApplication] ( )
119122 var retryCount = 0
120123 // Sometimes runningApplications returns 0 items.
@@ -151,7 +154,7 @@ enum Environment {
151154
152155extension AXError : Error { }
153156
154- extension AXUIElement {
157+ public extension AXUIElement {
155158 func copyValue< T> ( key: String , ofType _: T . Type = T . self) throws -> T {
156159 var value : AnyObject ?
157160 let error = AXUIElementCopyAttributeValue ( self , key as CFString , & value)
@@ -179,3 +182,42 @@ extension AXUIElement {
179182 throw error
180183 }
181184}
185+
186+ @discardableResult
187+ func runAppleScript( _ appleScript: String ) async throws -> String {
188+ let task = Process ( )
189+ task. launchPath = " /usr/bin/osascript "
190+ task. arguments = [ " -e " , appleScript]
191+ let outpipe = Pipe ( )
192+ task. standardOutput = outpipe
193+ task. standardError = Pipe ( )
194+
195+ return try await withUnsafeThrowingContinuation { continuation in
196+ do {
197+ task. terminationHandler = { _ in
198+ do {
199+ if let data = try outpipe. fileHandleForReading. readToEnd ( ) ,
200+ let content = String ( data: data, encoding: . utf8)
201+ {
202+ continuation. resume ( returning: content)
203+ return
204+ }
205+ continuation. resume ( returning: " " )
206+ } catch {
207+ continuation. resume ( throwing: error)
208+ }
209+ }
210+ try task. run ( )
211+ } catch {
212+ continuation. resume ( throwing: error)
213+ }
214+ }
215+ }
216+
217+ extension FileManager {
218+ func fileIsDirectory( atPath path: String ) -> Bool {
219+ var isDirectory : ObjCBool = false
220+ let exists = fileExists ( atPath: path, isDirectory: & isDirectory)
221+ return isDirectory. boolValue && exists
222+ }
223+ }
0 commit comments