1- import SuggestionModel
21import Foundation
32import LanguageClient
43import LanguageServerProtocol
54import Logger
65import Preferences
6+ import SuggestionModel
77import XPCShared
88
99public protocol GitHubCopilotAuthServiceType {
1010 func checkStatus( ) async throws -> GitHubCopilotAccountStatus
1111 func signInInitiate( ) async throws -> ( verificationUri: String , userCode: String )
12- func signInConfirm( userCode: String ) async throws -> ( username: String , status: GitHubCopilotAccountStatus )
12+ func signInConfirm( userCode: String ) async throws
13+ -> ( username: String , status: GitHubCopilotAccountStatus )
1314 func signOut( ) async throws -> GitHubCopilotAccountStatus
1415 func version( ) async throws -> String
1516}
@@ -37,6 +38,17 @@ protocol GitHubCopilotLSP {
3738 func sendNotification( _ notif: ClientNotification ) async throws
3839}
3940
41+ enum GitHubCopilotError : Error , LocalizedError {
42+ case languageServerNotInstalled
43+
44+ var errorDescription : String ? {
45+ switch self {
46+ case . languageServerNotInstalled:
47+ return " Language server is not installed. "
48+ }
49+ }
50+ }
51+
4052public class GitHubCopilotBaseService {
4153 let projectRootURL : URL
4254 var server : GitHubCopilotLSP
@@ -46,54 +58,52 @@ public class GitHubCopilotBaseService {
4658 server = designatedServer
4759 }
4860
49- init ( projectRootURL: URL ) {
61+ init ( projectRootURL: URL ) throws {
5062 self . projectRootURL = projectRootURL
51- server = {
52- let supportURL = FileManager . default. urls (
53- for: . applicationSupportDirectory,
54- in: . userDomainMask
55- ) . first!. appendingPathComponent ( " com.intii.CopilotForXcode " )
56- if !FileManager. default. fileExists ( atPath: supportURL. path) {
57- try ? FileManager . default
58- . createDirectory ( at: supportURL, withIntermediateDirectories: false )
59- }
63+ server = try {
64+ let urls = try GitHubCopilotBaseService . createFoldersIfNeeded ( )
6065 var userEnvPath = ProcessInfo . processInfo. userEnvironment [ " PATH " ] ?? " "
6166 if userEnvPath. isEmpty {
6267 userEnvPath = " /usr/bin:/usr/local/bin " // fallback
6368 }
6469 let executionParams : Process . ExecutionParameters
6570 let runner = UserDefaults . shared. value ( for: \. runNodeWith)
71+
72+ let agentJSURL = urls. executableURL. appendingPathComponent ( " copilot/dist/agent.js " )
73+ guard FileManager . default. fileExists ( atPath: agentJSURL. path) else {
74+ throw GitHubCopilotError . languageServerNotInstalled
75+ }
6676
6777 switch runner {
6878 case . bash:
6979 let nodePath = UserDefaults . shared. value ( for: \. nodePath)
7080 let command = [
7181 nodePath. isEmpty ? " node " : nodePath,
72- " \" \( Bundle . main . url ( forResource : " agent " , withExtension : " js " , subdirectory : " copilot/dist " ) ! . path) \" " ,
82+ " \" \( agentJSURL . path) \" " ,
7383 " --stdio " ,
7484 ] . joined ( separator: " " )
7585 executionParams = {
7686 Process . ExecutionParameters (
7787 path: " /bin/bash " ,
7888 arguments: [ " -i " , " -l " , " -c " , command] ,
7989 environment: [ : ] ,
80- currentDirectoryURL: supportURL
90+ currentDirectoryURL: urls . supportURL
8191 )
8292 } ( )
8393 case . shell:
8494 let shell = ProcessInfo . processInfo. userEnvironment [ " SHELL " ] ?? " /bin/bash "
8595 let nodePath = UserDefaults . shared. value ( for: \. nodePath)
8696 let command = [
8797 nodePath. isEmpty ? " node " : nodePath,
88- " \" \( Bundle . main . url ( forResource : " agent " , withExtension : " js " , subdirectory : " copilot/dist " ) ! . path) \" " ,
98+ " \" \( agentJSURL . path) \" " ,
8999 " --stdio " ,
90100 ] . joined ( separator: " " )
91101 executionParams = {
92102 Process . ExecutionParameters (
93103 path: shell,
94104 arguments: [ " -i " , " -l " , " -c " , command] ,
95105 environment: [ : ] ,
96- currentDirectoryURL: supportURL
106+ currentDirectoryURL: urls . supportURL
97107 )
98108 } ( )
99109 case . env:
@@ -103,17 +113,13 @@ public class GitHubCopilotBaseService {
103113 path: " /usr/bin/env " ,
104114 arguments: [
105115 nodePath. isEmpty ? " node " : nodePath,
106- Bundle . main. url (
107- forResource: " agent " ,
108- withExtension: " js " ,
109- subdirectory: " copilot/dist "
110- ) !. path,
116+ agentJSURL. path,
111117 " --stdio " ,
112118 ] ,
113119 environment: [
114120 " PATH " : userEnvPath,
115121 ] ,
116- currentDirectoryURL: supportURL
122+ currentDirectoryURL: urls . supportURL
117123 )
118124 } ( )
119125 }
@@ -149,12 +155,51 @@ public class GitHubCopilotBaseService {
149155 return server
150156 } ( )
151157 }
158+
159+ public static func createFoldersIfNeeded( ) throws -> (
160+ applicationSupportURL: URL ,
161+ gitHubCopilotURL: URL ,
162+ executableURL: URL ,
163+ supportURL: URL
164+ ) {
165+ let supportURL = FileManager . default. urls (
166+ for: . applicationSupportDirectory,
167+ in: . userDomainMask
168+ ) . first!. appendingPathComponent (
169+ Bundle . main
170+ . object ( forInfoDictionaryKey: " APPLICATION_SUPPORT_FOLDER " ) as! String
171+ )
172+
173+ if !FileManager. default. fileExists ( atPath: supportURL. path) {
174+ try ? FileManager . default
175+ . createDirectory ( at: supportURL, withIntermediateDirectories: false )
176+ }
177+ let gitHubCopilotFolderURL = supportURL. appendingPathComponent ( " GitHub Copilot " )
178+ if !FileManager. default. fileExists ( atPath: gitHubCopilotFolderURL. path) {
179+ try ? FileManager . default
180+ . createDirectory ( at: gitHubCopilotFolderURL, withIntermediateDirectories: false )
181+ }
182+ let supportFolderURL = gitHubCopilotFolderURL. appendingPathComponent ( " support " )
183+ if !FileManager. default. fileExists ( atPath: supportFolderURL. path) {
184+ try ? FileManager . default
185+ . createDirectory ( at: supportFolderURL, withIntermediateDirectories: false )
186+ }
187+ let executableFolderURL = gitHubCopilotFolderURL. appendingPathComponent ( " executable " )
188+ if !FileManager. default. fileExists ( atPath: executableFolderURL. path) {
189+ try ? FileManager . default
190+ . createDirectory ( at: executableFolderURL, withIntermediateDirectories: false )
191+ }
192+
193+ return ( supportURL, gitHubCopilotFolderURL, executableFolderURL, supportFolderURL)
194+ }
152195}
153196
154- public final class GitHubCopilotAuthService : GitHubCopilotBaseService , GitHubCopilotAuthServiceType {
155- public init ( ) {
197+ public final class GitHubCopilotAuthService : GitHubCopilotBaseService ,
198+ GitHubCopilotAuthServiceType
199+ {
200+ public init ( ) throws {
156201 let home = FileManager . default. homeDirectoryForCurrentUser
157- super. init ( projectRootURL: home)
202+ try super. init ( projectRootURL: home)
158203 Task {
159204 try ? await server. sendRequest ( GitHubCopilotRequest . SetEditorInfo ( ) )
160205 }
@@ -172,7 +217,8 @@ public final class GitHubCopilotAuthService: GitHubCopilotBaseService, GitHubCop
172217 public func signInConfirm( userCode: String ) async throws
173218 -> ( username: String , status: GitHubCopilotAccountStatus )
174219 {
175- let result = try await server. sendRequest ( GitHubCopilotRequest . SignInConfirm ( userCode: userCode) )
220+ let result = try await server
221+ . sendRequest ( GitHubCopilotRequest . SignInConfirm ( userCode: userCode) )
176222 return ( result. user, result. status)
177223 }
178224
@@ -185,9 +231,11 @@ public final class GitHubCopilotAuthService: GitHubCopilotBaseService, GitHubCop
185231 }
186232}
187233
188- public final class GitHubCopilotSuggestionService : GitHubCopilotBaseService , GitHubCopilotSuggestionServiceType {
189- override public init ( projectRootURL: URL = URL ( fileURLWithPath: " / " ) ) {
190- super. init ( projectRootURL: projectRootURL)
234+ public final class GitHubCopilotSuggestionService : GitHubCopilotBaseService ,
235+ GitHubCopilotSuggestionServiceType
236+ {
237+ override public init ( projectRootURL: URL = URL ( fileURLWithPath: " / " ) ) throws {
238+ try super. init ( projectRootURL: projectRootURL)
191239 }
192240
193241 override init ( designatedServer: GitHubCopilotLSP ) {
@@ -312,3 +360,4 @@ extension InitializingServer: GitHubCopilotLSP {
312360 try await sendRequest ( endpoint. request)
313361 }
314362}
363+
0 commit comments