Skip to content

Commit b8ac026

Browse files
committed
Merge branch 'feature/codeium-settings' into develop
2 parents 6ffcf31 + 33dbe18 commit b8ac026

20 files changed

Lines changed: 732 additions & 50 deletions

File tree

Config.debug.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "Version.xcconfig"
22
SLASH = /
33

4+
PRODUCT_NAME = Copilot for Xcode Dev
45
BUNDLE_IDENTIFIER_BASE = dev.com.intii.CopilotForXcode
56
EXTENSION_BUNDLE_NAME = Copilot Dev
67
SPARKLE_FEED_URL = http:$(SLASH)$(SLASH)127.0.0.1:9433/appcast.xml

Config.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "Version.xcconfig"
22
SLASH = /
33

4+
HOST_APP_NAME = Copilot for Xcode
45
BUNDLE_IDENTIFIER_BASE = com.intii.CopilotForXcode
56
EXTENSION_BUNDLE_NAME = Copilot
67
SPARKLE_FEED_URL = https:$(SLASH)$(SLASH)raw.githubusercontent.com/intitni/CopilotForXcode/main/appcast.xml

Copilot for Xcode.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Copilot for Xcode/Copilot_for_Xcode.entitlements

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
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>com.apple.security.app-sandbox</key>
6+
<false/>
57
<key>com.apple.security.application-groups</key>
68
<array>
79
<string>$(TeamIdentifierPrefix)group.$(BUNDLE_IDENTIFIER_BASE)</string>
810
</array>
9-
<key>com.apple.security.app-sandbox</key>
10-
<false/>
1111
<key>com.apple.security.files.user-selected.read-only</key>
1212
<true/>
13+
<key>keychain-access-groups</key>
14+
<array>
15+
<string>$(AppIdentifierPrefix)$(BUNDLE_IDENTIFIER_BASE).Shared</string>
16+
</array>
1317
</dict>
1418
</plist>

Copilot-for-Xcode-Info.plist

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
<dict>
55
<key>APPLICATION_SUPPORT_FOLDER</key>
66
<string>$(APPLICATION_SUPPORT_FOLDER)</string>
7+
<key>APP_ID_PREFIX</key>
8+
<string>$(AppIdentifierPrefix)</string>
79
<key>BUNDLE_IDENTIFIER_BASE</key>
810
<string>$(BUNDLE_IDENTIFIER_BASE)</string>
911
<key>EXTENSION_BUNDLE_NAME</key>
1012
<string>$(EXTENSION_BUNDLE_NAME)</string>
13+
<key>HOST_APP_NAME</key>
14+
<string>$(HOST_APP_NAME)</string>
1115
<key>SUEnableJavaScript</key>
1216
<string>YES</string>
1317
<key>SUFeedURL</key>
1418
<string>$(SPARKLE_FEED_URL)</string>
1519
<key>SUPublicEDKey</key>
1620
<string>$(SPARKLE_PUBLIC_KEY)</string>
21+
<key>TEAM_ID_PREFIX</key>
22+
<string>$(TeamIdentifierPrefix)</string>
1723
</dict>
1824
</plist>

Core/Package.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ let package = Package(
5656
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.1.0"),
5757
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.0.0"),
5858
.package(url: "https://github.com/alfianlosari/GPTEncoder", from: "1.0.4"),
59+
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.2"),
5960
],
6061
targets: [
6162
// MARK: - Main
@@ -124,8 +125,9 @@ let package = Package(
124125
"Preferences",
125126
"Client",
126127
"GitHubCopilotService",
128+
"CodeiumService",
127129
"SuggestionModel",
128-
"LaunchAgentManager"
130+
"LaunchAgentManager",
129131
]
130132
),
131133

@@ -254,7 +256,13 @@ let package = Package(
254256

255257
.target(
256258
name: "CodeiumService",
257-
dependencies: ["LanguageClient", "SuggestionModel", "Preferences"]
259+
dependencies: [
260+
"LanguageClient",
261+
"SuggestionModel",
262+
"Preferences",
263+
"KeychainAccess",
264+
"Terminal"
265+
]
258266
),
259267
]
260268
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import Foundation
2+
import KeychainAccess
3+
4+
public final class CodeiumAuthService {
5+
public init() {}
6+
let codeiumKeyKey = "codeiumKey"
7+
let keychain: Keychain = {
8+
let info = Bundle.main.infoDictionary
9+
return Keychain(
10+
service: info?["BUNDLE_IDENTIFIER_BASE"] as? String ?? "",
11+
accessGroup: "\(info?["APP_ID_PREFIX"] as? String ?? "")\(info?["BUNDLE_IDENTIFIER_BASE"] as? String ?? "").Shared"
12+
)
13+
}()
14+
15+
var key: String? { try? keychain.getString(codeiumKeyKey) }
16+
17+
public var isSignedIn: Bool { return key != nil }
18+
19+
public func signIn(token: String) async throws {
20+
let key = try await generate(token: token)
21+
let info = Bundle.main.infoDictionary
22+
try keychain.set(key, key: codeiumKeyKey)
23+
}
24+
25+
public func signOut() async throws {
26+
try keychain.remove(codeiumKeyKey)
27+
}
28+
29+
struct GenerateKeyRequestBody: Codable {
30+
var firebase_id_token: String
31+
}
32+
33+
struct GenerateKeyResponseBody: Codable {
34+
var api_key: String
35+
}
36+
37+
struct GenerateKeyErrorResponseBody: Codable, Error, LocalizedError {
38+
var detail: String
39+
var errorDescription: String? { detail }
40+
}
41+
42+
func generate(token: String) async throws -> String {
43+
var request = URLRequest(url: URL(string: "https://api.codeium.com/register_user/")!)
44+
request.httpMethod = "POST"
45+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
46+
let requestBody = GenerateKeyRequestBody(firebase_id_token: token)
47+
let requestData = try JSONEncoder().encode(requestBody)
48+
request.httpBody = requestData
49+
let (data, _) = try await URLSession.shared.data(for: request)
50+
do {
51+
let response = try JSONDecoder().decode(GenerateKeyResponseBody.self, from: data)
52+
return response.api_key
53+
} catch {
54+
if let response = try? JSONDecoder()
55+
.decode(GenerateKeyErrorResponseBody.self, from: data)
56+
{
57+
throw response
58+
}
59+
throw error
60+
}
61+
}
62+
}
63+
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import Foundation
2+
import Terminal
3+
4+
public struct CodeiumInstallationManager {
5+
private static var isInstalling = false
6+
let latestSupportedVersion: String = "1.2.9"
7+
8+
public init() {}
9+
10+
public enum InstallationStatus {
11+
case notInstalled
12+
case installed(String)
13+
case outdated(current: String, latest: String)
14+
case unsupported(current: String, latest: String)
15+
}
16+
17+
public func checkInstallation() -> InstallationStatus {
18+
guard let urls = try? CodeiumSuggestionService.createFoldersIfNeeded()
19+
else { return .notInstalled }
20+
let executableFolderURL = urls.executableURL
21+
let binaryURL = executableFolderURL.appendingPathComponent("language_server")
22+
let versionFileURL = executableFolderURL.appendingPathComponent("version")
23+
24+
if !FileManager.default.fileExists(atPath: binaryURL.path) {
25+
return .notInstalled
26+
}
27+
28+
if FileManager.default.fileExists(atPath: versionFileURL.path),
29+
let versionData = try? Data(contentsOf: versionFileURL),
30+
let version = String(data: versionData, encoding: .utf8)
31+
{
32+
switch version.compare(latestSupportedVersion) {
33+
case .orderedAscending:
34+
return .outdated(current: version, latest: latestSupportedVersion)
35+
case .orderedSame:
36+
return .installed(version)
37+
case .orderedDescending:
38+
return .unsupported(current: version, latest: latestSupportedVersion)
39+
}
40+
}
41+
42+
return .outdated(current: "Unknown", latest: latestSupportedVersion)
43+
}
44+
45+
public enum InstallationStep {
46+
case downloading
47+
case uninstalling
48+
case decompressing
49+
case done
50+
}
51+
52+
public func installLatestVersion() -> AsyncThrowingStream<InstallationStep, Error> {
53+
AsyncThrowingStream<InstallationStep, Error> { continuation in
54+
Task {
55+
guard !CodeiumInstallationManager.isInstalling else {
56+
continuation.finish(throwing: CodeiumError.languageServiceIsInstalling)
57+
return
58+
}
59+
CodeiumInstallationManager.isInstalling = true
60+
defer { CodeiumInstallationManager.isInstalling = false }
61+
do {
62+
continuation.yield(.downloading)
63+
let urls = try CodeiumSuggestionService.createFoldersIfNeeded()
64+
let urlString =
65+
"https://github.com/Exafunction/codeium/releases/download/language-server-v\(latestSupportedVersion)/language_server_macos_\(isAppleSilicon() ? "arm" : "x64").gz"
66+
guard let url = URL(string: urlString) else { return }
67+
68+
// download
69+
let (fileURL, _) = try await URLSession.shared.download(from: url)
70+
let targetURL = urls.executableURL.appendingPathComponent("language_server")
71+
.appendingPathExtension("gz")
72+
try FileManager.default.copyItem(at: fileURL, to: targetURL)
73+
defer { try? FileManager.default.removeItem(at: targetURL) }
74+
75+
continuation.yield(.uninstalling)
76+
try await uninstall()
77+
78+
continuation.yield(.decompressing)
79+
// extract file
80+
let terminal = Terminal()
81+
_ = try await terminal.runCommand(
82+
"/usr/bin/gunzip",
83+
arguments: [targetURL.path],
84+
environment: [:]
85+
)
86+
87+
// update permission 755
88+
try FileManager.default.setAttributes(
89+
[.posixPermissions: 0o755],
90+
ofItemAtPath: targetURL.deletingPathExtension().path
91+
)
92+
93+
// create version file
94+
let data = latestSupportedVersion.data(using: .utf8)
95+
FileManager.default.createFile(
96+
atPath: urls.executableURL.appendingPathComponent("version").path,
97+
contents: data
98+
)
99+
100+
continuation.yield(.done)
101+
continuation.finish()
102+
} catch {
103+
continuation.finish(throwing: error)
104+
}
105+
}
106+
}
107+
}
108+
109+
public func uninstall() async throws {
110+
guard let urls = try? CodeiumSuggestionService.createFoldersIfNeeded()
111+
else { return }
112+
let executableFolderURL = urls.executableURL
113+
let binaryURL = executableFolderURL.appendingPathComponent("language_server")
114+
let versionFileURL = executableFolderURL.appendingPathComponent("version")
115+
if FileManager.default.fileExists(atPath: binaryURL.path) {
116+
try FileManager.default.removeItem(at: binaryURL)
117+
}
118+
if FileManager.default.fileExists(atPath: versionFileURL.path) {
119+
try FileManager.default.removeItem(at: versionFileURL)
120+
}
121+
}
122+
}
123+
124+
func isAppleSilicon() -> Bool {
125+
var result = false
126+
#if arch(arm64)
127+
result = true
128+
#endif
129+
return result
130+
}
131+

Core/Sources/CodeiumService/CodeiumLanguageServer.swift

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import JSONRPC
33
import LanguageClient
44
import LanguageServerProtocol
55
import Logger
6+
import Preferences
67

78
protocol CodeiumLSP {
89
func sendRequest<E: CodeiumRequestType>(_ endpoint: E) async throws -> E.Response
@@ -106,7 +107,9 @@ final class CodeiumLanguageServer {
106107

107108
deinit {
108109
process.terminationHandler = nil
109-
process.terminate()
110+
if process.isRunning {
111+
process.terminate()
112+
}
110113
transport.close()
111114
}
112115

@@ -133,16 +136,23 @@ extension CodeiumLanguageServer: CodeiumLSP {
133136
let response = try JSONDecoder().decode(E.Response.self, from: data)
134137
return response
135138
} catch {
136-
dump(error)
137-
Logger.codeium.error(error.localizedDescription)
139+
if UserDefaults.shared.value(for: \.codeiumVerboseLog) {
140+
dump(error)
141+
Logger.codeium.error(error.localizedDescription)
142+
}
138143
throw error
139144
}
140145
} else {
141146
do {
142147
let error = try JSONDecoder().decode(CodeiumResponseError.self, from: data)
143-
throw error
148+
if UserDefaults.shared.value(for: \.codeiumVerboseLog) {
149+
Logger.codeium.error(error.message)
150+
}
151+
throw CancellationError()
144152
} catch {
145-
Logger.codeium.error(error.localizedDescription)
153+
if UserDefaults.shared.value(for: \.codeiumVerboseLog) {
154+
Logger.codeium.error(error.localizedDescription)
155+
}
146156
throw error
147157
}
148158
}
@@ -201,9 +211,9 @@ final class IOTransport {
201211
return
202212
}
203213

204-
#if DEBUG
205-
self.forwardDataToHandler(data)
206-
#endif
214+
if UserDefaults.shared.value(for: \.codeiumVerboseLog) {
215+
self.forwardDataToHandler(data)
216+
}
207217
}
208218

209219
stderrPipe.fileHandleForReading.readabilityHandler = { [unowned self] handle in
@@ -213,9 +223,9 @@ final class IOTransport {
213223
return
214224
}
215225

216-
#if DEBUG
217-
self.forwardErrorDataToHandler(data)
218-
#endif
226+
if UserDefaults.shared.value(for: \.codeiumVerboseLog) {
227+
self.forwardErrorDataToHandler(data)
228+
}
219229
}
220230
}
221231

Core/Sources/CodeiumService/CodeiumModels.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct CodeiumEditorOptions: Codable {
110110
struct Metadata: Codable {
111111
var ide_name: String
112112
var ide_version: String
113-
var extension_name: String
113+
// var extension_name: String
114114
var extension_version: String
115115
var api_key: String
116116

0 commit comments

Comments
 (0)