Skip to content

Commit 10da585

Browse files
committed
Adjust application support folder structure, launch lsp from it
1 parent af30810 commit 10da585

File tree

12 files changed

+167
-69
lines changed

12 files changed

+167
-69
lines changed

Config.debug.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ BUNDLE_IDENTIFIER_BASE = dev.com.intii.CopilotForXcode
55
EXTENSION_BUNDLE_NAME = Copilot Dev
66
SPARKLE_FEED_URL = http:$(SLASH)$(SLASH)127.0.0.1:9433/appcast.xml
77
SPARKLE_PUBLIC_KEY = WDzm5GHnc6c8kjeJEgX5GuGiPpW6Lc/ovGjLnrrZvPY=
8+
APPLICATION_SUPPORT_FOLDER = dev.com.intii.CopilotForXcode

Config.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ BUNDLE_IDENTIFIER_BASE = com.intii.CopilotForXcode
55
EXTENSION_BUNDLE_NAME = Copilot
66
SPARKLE_FEED_URL = https:$(SLASH)$(SLASH)raw.githubusercontent.com/intitni/CopilotForXcode/main/appcast.xml
77
SPARKLE_PUBLIC_KEY = WDzm5GHnc6c8kjeJEgX5GuGiPpW6Lc/ovGjLnrrZvPY=
8+
APPLICATION_SUPPORT_FOLDER = com.intii.CopilotForXcode

Copilot-for-Xcode-Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>APPLICATION_SUPPORT_FOLDER</key>
6+
<string>$(APPLICATION_SUPPORT_FOLDER)</string>
57
<key>BUNDLE_IDENTIFIER_BASE</key>
68
<string>$(BUNDLE_IDENTIFIER_BASE)</string>
79
<key>EXTENSION_BUNDLE_NAME</key>

Core/Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ let package = Package(
6868
"Logger",
6969
"ChatService",
7070
"PromptToCodeService",
71+
"ServiceUpdateMigration",
7172
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
7273
]
7374
),
@@ -173,6 +174,10 @@ let package = Package(
173174
]
174175
),
175176
.target(name: "AXExtension"),
177+
.target(
178+
name: "ServiceUpdateMigration",
179+
dependencies: ["Preferences", "GitHubCopilotService"]
180+
),
176181

177182
// MARK: - GitHub Copilot
178183

@@ -203,4 +208,3 @@ let package = Package(
203208
]
204209
)
205210

206-

Core/Sources/Environment/Environment.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,12 @@ public enum Environment {
125125
return try await fetchCurrentFileURL()
126126
}
127127

128-
public static var createSuggestionService: (_ projectRootURL: URL)
129-
-> SuggestionServiceType = { projectRootURL in
130-
SuggestionService(projectRootURL: projectRootURL)
131-
}
128+
public static var createSuggestionService: (
129+
_ projectRootURL: URL,
130+
_ onServiceLaunched: @escaping (SuggestionServiceType) -> Void
131+
) -> SuggestionServiceType = { projectRootURL, onServiceLaunched in
132+
SuggestionService(projectRootURL: projectRootURL, onServiceLaunched: onServiceLaunched)
133+
}
132134

133135
public static var triggerAction: (_ name: String) async throws -> Void = { name in
134136
guard let activeXcode = ActiveApplicationMonitor.activeXcode

Core/Sources/GitHubCopilotService/GitHubCopilotService.swift

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import SuggestionModel
21
import Foundation
32
import LanguageClient
43
import LanguageServerProtocol
54
import Logger
65
import Preferences
6+
import SuggestionModel
77
import XPCShared
88

99
public 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+
4052
public 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+

Core/Sources/PromptToCodeService/CopilotPromptToCodeAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ final class CopilotPromptToCodeAPI: PromptToCodeAPI {
2121
allCode: String,
2222
extraSystemPrompt: String?
2323
) async throws -> AsyncThrowingStream<(code: String, description: String), Error> {
24-
let copilotService = GitHubCopilotSuggestionService(projectRootURL: projectRootURL)
24+
let copilotService = try GitHubCopilotSuggestionService(projectRootURL: projectRootURL)
2525
let relativePath = {
2626
let filePath = fileURL.path
2727
let rootPath = projectRootURL.path

Core/Sources/Service/Workspace.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ final class Workspace {
118118
}
119119

120120
if _suggestionService == nil {
121-
_suggestionService = Environment.createSuggestionService(projectRootURL)
121+
_suggestionService = Environment.createSuggestionService(projectRootURL) {
122+
[weak self] service in
123+
guard let self else { return }
124+
for (_, filespace) in filespaces {
125+
notifyOpenFile(filespace: filespace)
126+
}
127+
}
122128
}
123129
return _suggestionService
124130
}

Core/Sources/Service/XPCService.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,21 @@ public class XPCService: NSObject, XPCServiceProtocol {
3232
// MARK: - Copilot Auth
3333

3434
@ServiceActor
35-
lazy var gitHubCopilotAuthService: GitHubCopilotAuthServiceType = GitHubCopilotAuthService()
35+
var _gitHubCopilotAuthService: GitHubCopilotAuthServiceType?
36+
@ServiceActor
37+
var gitHubCopilotAuthService: GitHubCopilotAuthServiceType {
38+
get throws {
39+
if let _gitHubCopilotAuthService { return _gitHubCopilotAuthService }
40+
let newService = try GitHubCopilotAuthService()
41+
_gitHubCopilotAuthService = newService
42+
return newService
43+
}
44+
}
3645

3746
public func checkStatus(withReply reply: @escaping (String?, Error?) -> Void) {
3847
Task { @ServiceActor in
3948
do {
40-
let status = try await gitHubCopilotAuthService.checkStatus()
49+
let status = try await (try gitHubCopilotAuthService).checkStatus()
4150
reply(status.rawValue, nil)
4251
} catch {
4352
reply(nil, NSError.from(error))
@@ -48,7 +57,7 @@ public class XPCService: NSObject, XPCServiceProtocol {
4857
public func signInInitiate(withReply reply: @escaping (String?, String?, Error?) -> Void) {
4958
Task { @ServiceActor in
5059
do {
51-
let (verificationLink, userCode) = try await gitHubCopilotAuthService
60+
let (verificationLink, userCode) = try await (try gitHubCopilotAuthService)
5261
.signInInitiate()
5362
reply(verificationLink, userCode, nil)
5463
} catch {
@@ -63,7 +72,7 @@ public class XPCService: NSObject, XPCServiceProtocol {
6372
) {
6473
Task { @ServiceActor in
6574
do {
66-
let (username, status) = try await gitHubCopilotAuthService
75+
let (username, status) = try await (try gitHubCopilotAuthService)
6776
.signInConfirm(userCode: userCode)
6877
reply(username, status.rawValue, nil)
6978
} catch {

0 commit comments

Comments
 (0)