diff --git a/Copilot for Xcode/App.swift b/Copilot for Xcode/App.swift
index 094e32d..a1cf045 100644
--- a/Copilot for Xcode/App.swift
+++ b/Copilot for Xcode/App.swift
@@ -1,6 +1,7 @@
import Client
import HostApp
import LaunchAgentManager
+import SharedUIComponents
import SwiftUI
import UpdateChecker
import XPCShared
@@ -21,6 +22,7 @@ struct CopilotForXcodeApp: App {
UserDefaults.setupDefaultSettings()
}
.environment(\.updateChecker, UpdateChecker(hostBundle: Bundle.main))
+ .copilotIntroSheet()
}
}
}
diff --git a/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/Contents.json b/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/Contents.json
new file mode 100644
index 0000000..2e35661
--- /dev/null
+++ b/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "copilot.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/copilot.svg b/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/copilot.svg
new file mode 100644
index 0000000..8284dce
--- /dev/null
+++ b/Copilot for Xcode/Assets.xcassets/CopilotLogo.imageset/copilot.svg
@@ -0,0 +1,12 @@
+
diff --git a/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Contents.json b/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Contents.json
new file mode 100644
index 0000000..1f7fbbe
--- /dev/null
+++ b/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "Icon.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties": {
+ "preserves-vector-representation": true
+ }
+}
diff --git a/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Icon.svg b/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Icon.svg
new file mode 100644
index 0000000..1520416
--- /dev/null
+++ b/Copilot for Xcode/Assets.xcassets/GitHubMark.imageset/Icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/Core/Sources/HostApp/FeatureSettings/Suggestion/SuggesionSettingProxyView.swift b/Core/Sources/HostApp/FeatureSettings/Suggestion/SuggesionSettingProxyView.swift
index caa217b..7f8488c 100644
--- a/Core/Sources/HostApp/FeatureSettings/Suggestion/SuggesionSettingProxyView.swift
+++ b/Core/Sources/HostApp/FeatureSettings/Suggestion/SuggesionSettingProxyView.swift
@@ -9,8 +9,7 @@ struct SuggesionSettingProxyView: View {
class Settings: ObservableObject {
@AppStorage("username") var username: String = ""
- @AppStorage(\.gitHubCopilotProxyHost) var gitHubCopilotProxyHost
- @AppStorage(\.gitHubCopilotProxyPort) var gitHubCopilotProxyPort
+ @AppStorage(\.gitHubCopilotProxyUrl) var gitHubCopilotProxyUrl
@AppStorage(\.gitHubCopilotProxyUsername) var gitHubCopilotProxyUsername
@AppStorage(\.gitHubCopilotProxyPassword) var gitHubCopilotProxyPassword
@AppStorage(\.gitHubCopilotUseStrictSSL) var gitHubCopilotUseStrictSSL
@@ -39,13 +38,10 @@ struct SuggesionSettingProxyView: View {
Form {
TextField(
- text: $settings.gitHubCopilotProxyHost,
- prompt: Text("xxx.xxx.xxx.xxx, leave it blank to disable proxy.")
+ text: $settings.gitHubCopilotProxyUrl,
+ prompt: Text("http://host:port")
) {
- Text("Proxy host")
- }
- TextField(text: $settings.gitHubCopilotProxyPort, prompt: Text("80")) {
- Text("Proxy port")
+ Text("Proxy URL")
}
TextField(text: $settings.gitHubCopilotProxyUsername) {
Text("Proxy username")
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
index a7b16c4..4c8818a 100644
--- a/DEVELOPMENT.md
+++ b/DEVELOPMENT.md
@@ -8,22 +8,6 @@ Requires Node installed and `npm` available on your system path, e.g.
sudo ln -s `which npm` /usr/local/bin
```
-## Local Language Server
-
-To run the language server locally create a `Config.local.xcconfig` file with two config values:
-
-```xcconfig
-LANGUAGE_SERVER_PATH=~/code/copilot-client
-NODE_PATH=/opt/path/to/node
-```
-
-`LANGUAGE_SERVER_PATH` should point to the path where the copilot-client repo is
-checked out and `$(LANGUAGE_SERVER_PATH)/dist/language-server.js` must exist
-(run `npm run build`).
-
-`NODE_PATH` should point to where node is installed. It can be omitted if
-`/usr/bin/env node` will resolves directly.
-
## Targets
### Copilot for Xcode
@@ -79,4 +63,4 @@ The source code mostly follows the [Ray Wenderlich Style Guide](https://github.c
## App Versioning
-The app version and all targets' version in controlled by `Version.xcconfig`.
\ No newline at end of file
+The app version and all targets' version in controlled by `Version.xcconfig`.
diff --git a/Docs/demo.gif b/Docs/demo.gif
index 5310fa1..6e10eca 100644
Binary files a/Docs/demo.gif and b/Docs/demo.gif differ
diff --git a/README.md b/README.md
index 1e9b13c..cf569b5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
#
GitHub Copilot For Xcode
-
+
GitHub Copilot for Xcode is macOS application and Xcode extension that enables
using GitHub Copilot code completions in Xcode.
diff --git a/Server/package-lock.json b/Server/package-lock.json
index 35e4cce..936d91a 100644
--- a/Server/package-lock.json
+++ b/Server/package-lock.json
@@ -8,13 +8,13 @@
"name": "@github/copilot-xcode",
"version": "0.0.1",
"dependencies": {
- "@github/copilot-language-server": "^1.234.0"
+ "@github/copilot-language-server": "^1.235.0"
}
},
"node_modules/@github/copilot-language-server": {
- "version": "1.234.0",
- "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.234.0.tgz",
- "integrity": "sha512-uNjSZZawr5uNE6+3IVNI0/mnImBqDFt8bEdz7zNLp3a8t0LcCJPA7rW0GR3r3LZ9wuIwjgz+XuS938q9lmN1Jg==",
+ "version": "1.235.0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-language-server/-/copilot-language-server-1.235.0.tgz",
+ "integrity": "sha512-QvBoh0qx9yBPBKxxfL2oWxrL5Kl4scniB7QpUJmIkudagc/23epZIo1fZXwHeYYzmnmOpjMATE5PVOieokPXWA==",
"bin": {
"copilot-language-server": "dist/language-server.js"
}
diff --git a/Server/package.json b/Server/package.json
index d281b5d..32e9d20 100644
--- a/Server/package.json
+++ b/Server/package.json
@@ -4,6 +4,6 @@
"description": "Package for downloading @github/copilot-language-server",
"private": true,
"dependencies": {
- "@github/copilot-language-server": "^1.234.0"
+ "@github/copilot-language-server": "^1.235.0"
}
}
diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift
index 4263774..159384b 100644
--- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift
+++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift
@@ -49,72 +49,41 @@ public struct GitHubCopilotCodeSuggestion: Codable, Equatable {
public var displayText: String
}
-enum GitHubCopilotRequest {
- // TODO migrate from setEditorInfo to didConfigurationChange
- struct SetEditorInfo: GitHubCopilotRequestType {
- struct Response: Codable {}
-
- let versionNumber = JSONValue(stringLiteral: Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "")
- let xcodeVersion = JSONValue(stringLiteral: SystemInfo().xcodeVersion() ?? "")
-
- var networkProxy: JSONValue? {
- let host = UserDefaults.shared.value(for: \.gitHubCopilotProxyHost)
- if host.isEmpty { return nil }
- var port = UserDefaults.shared.value(for: \.gitHubCopilotProxyPort)
- if port.isEmpty { port = "80" }
- let username = UserDefaults.shared.value(for: \.gitHubCopilotProxyUsername)
- if username.isEmpty {
- return .hash([
- "host": .string(host),
- "port": .number(Double(Int(port) ?? 80)),
- "rejectUnauthorized": .bool(UserDefaults.shared
- .value(for: \.gitHubCopilotUseStrictSSL)),
- ])
- } else {
- return .hash([
- "host": .string(host),
- "port": .number(Double(Int(port) ?? 80)),
- "rejectUnauthorized": .bool(UserDefaults.shared
- .value(for: \.gitHubCopilotUseStrictSSL)),
- "username": .string(username),
- "password": .string(UserDefaults.shared
- .value(for: \.gitHubCopilotProxyPassword)),
-
- ])
- }
- }
-
- var authProvider: JSONValue? {
- var dict: [String: JSONValue] = [:]
- let enterpriseURI = UserDefaults.shared.value(for: \.gitHubCopilotEnterpriseURI)
- if !enterpriseURI.isEmpty {
- dict["url"] = .string(enterpriseURI)
- }
+public func editorConfiguration() -> JSONValue {
+ var proxyAuthorization: String? {
+ let username = UserDefaults.shared.value(for: \.gitHubCopilotProxyUsername)
+ if username.isEmpty { return nil }
+ let password = UserDefaults.shared.value(for: \.gitHubCopilotProxyPassword)
+ return "\(username):\(password)"
+ }
- if dict.isEmpty { return nil }
- return .hash(dict)
+ var http: JSONValue? {
+ var d: [String: JSONValue] = [:]
+ let proxy = UserDefaults.shared.value(for: \.gitHubCopilotProxyUrl)
+ if !proxy.isEmpty {
+ d["proxy"] = .string(proxy)
}
-
- var request: ClientRequest {
- var dict: [String: JSONValue] = [
- "editorInfo": .hash([
- "name": "Xcode",
- "version": xcodeVersion,
- ]),
- "editorPluginInfo": .hash([
- "name": "copilot-xcode",
- "version": versionNumber,
- ]),
- ]
-
- dict["authProvider"] = authProvider
- dict["networkProxy"] = networkProxy
-
- return .custom("setEditorInfo", .hash(dict))
+ if let proxyAuthorization = proxyAuthorization {
+ d["proxyAuthorization"] = .string(proxyAuthorization)
}
+ d["proxyStrictSSL"] = .bool(UserDefaults.shared.value(for: \.gitHubCopilotUseStrictSSL))
+ if d.isEmpty { return nil }
+ return .hash(d)
+ }
+ var authProvider: JSONValue? {
+ let enterpriseURI = UserDefaults.shared.value(for: \.gitHubCopilotEnterpriseURI)
+ if enterpriseURI.isEmpty { return nil }
+ return .hash([ "uri": .string(enterpriseURI) ])
}
+ var d: [String: JSONValue] = [:]
+ if let http { d["http"] = http }
+ if let authProvider { d["github-enterprise"] = authProvider }
+ return .hash(d)
+}
+
+enum GitHubCopilotRequest {
struct GetVersion: GitHubCopilotRequestType {
struct Response: Codable {
var version: String
diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
index 24dd5bf..806fd90 100644
--- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
+++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift
@@ -172,10 +172,6 @@ public class GitHubCopilotBaseService {
return InitializeParams(
processId: Int(ProcessInfo.processInfo.processIdentifier),
- clientInfo: .init(
- name: "copilot-xcode",
- version: "1.5.0.5206-nightly"
- ),
locale: nil,
rootPath: projectRootURL.path,
rootUri: projectRootURL.path,
@@ -188,6 +184,7 @@ public class GitHubCopilotBaseService {
"name": "copilot-xcode",
"version": versionNumber,
],
+ "editorConfiguration": editorConfiguration(),
],
capabilities: capabilities,
trace: .off,
@@ -207,11 +204,13 @@ public class GitHubCopilotBaseService {
let notifications = NotificationCenter.default
.notifications(named: .gitHubCopilotShouldRefreshEditorInformation)
Task { [weak self] in
- _ = try? await server.sendRequest(GitHubCopilotRequest.SetEditorInfo())
-
for await _ in notifications {
guard self != nil else { return }
- _ = try? await server.sendRequest(GitHubCopilotRequest.SetEditorInfo())
+ _ = try? await server.sendNotification(
+ .workspaceDidChangeConfiguration(
+ .init(settings: editorConfiguration())
+ )
+ )
}
}
}
diff --git a/Tool/Sources/Preferences/Keys.swift b/Tool/Sources/Preferences/Keys.swift
index 92c4dfb..eb1b3b7 100644
--- a/Tool/Sources/Preferences/Keys.swift
+++ b/Tool/Sources/Preferences/Keys.swift
@@ -94,10 +94,23 @@ public struct UserDefaultPreferenceKeys {
)
// MARK: Completion Hint Shown
+
public let completionHintShown = PreferenceKey(
defaultValue: false,
key: "CompletionHintShown"
)
+
+ // MARK: First Time Intro Interface
+
+ public let introLastShownVersion = PreferenceKey(
+ defaultValue: "",
+ key: "IntroLastShownVersion"
+ )
+
+ public let hideIntro = PreferenceKey(
+ defaultValue: false,
+ key: "HideIntro"
+ )
}
// MARK: - Prompt to Code
@@ -512,13 +525,9 @@ public extension UserDefaultPreferenceKeys {
// MARK: - Feature
public extension UserDefaultPreferenceKeys {
-
- var gitHubCopilotProxyHost: PreferenceKey {
- .init(defaultValue: "", key: "GitHubCopilotProxyHost")
- }
-
- var gitHubCopilotProxyPort: PreferenceKey {
- .init(defaultValue: "", key: "GitHubCopilotProxyPort")
+
+ var gitHubCopilotProxyUrl: PreferenceKey {
+ .init(defaultValue: "", key: "GitHubCopilotProxyUrl")
}
var gitHubCopilotUseStrictSSL: PreferenceKey {
diff --git a/Tool/Sources/SharedUIComponents/CopilotIntroSheet.swift b/Tool/Sources/SharedUIComponents/CopilotIntroSheet.swift
new file mode 100644
index 0000000..a6008b9
--- /dev/null
+++ b/Tool/Sources/SharedUIComponents/CopilotIntroSheet.swift
@@ -0,0 +1,113 @@
+import SwiftUI
+import AppKit
+
+struct CopilotIntroItem: View {
+ let heading: String
+ let text: String
+ let image: Image
+
+ public init(imageName: String, heading: String, text: String) {
+ self.init(imageObject: Image(imageName), heading: heading, text: text)
+ }
+
+ public init(systemImage: String, heading: String, text: String) {
+ self.init(imageObject: Image(systemName: systemImage), heading: heading, text: text)
+ }
+
+ public init(imageObject: Image, heading: String, text: String) {
+ self.heading = heading
+ self.text = text
+ self.image = imageObject
+ }
+
+ var body: some View {
+ HStack(spacing: 16) {
+ image
+ .resizable()
+ .renderingMode(.template)
+ .foregroundColor(Color(red: 0.0353, green: 0.4118, blue: 0.8549))
+ .scaledToFit()
+ .frame(width: 28, height: 28)
+ VStack(alignment: .leading, spacing: 5) {
+ Text(heading)
+ .font(.system(size: 11, weight: .bold))
+ Text(text)
+ .font(.system(size: 11))
+ .lineSpacing(3)
+ }
+ }
+ }
+}
+
+public struct CopilotIntroSheet: View {
+ let content: Content
+ let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
+ @AppStorage(\.hideIntro) var hideIntro
+ @AppStorage(\.introLastShownVersion) var introLastShownVersion
+ @State var isPresented = false
+
+ public var body: some View {
+ content.sheet(isPresented: $isPresented) {
+ VStack {
+ Image(nsImage: NSImage(named: "AppIcon") ?? NSImage())
+ .resizable()
+ .scaledToFit()
+ .frame(width: 64, height: 64)
+ .padding(.bottom, 24)
+ Text("Welcome to Copilot for Xcode!")
+ .font(.title)
+ .padding(.bottom, 45)
+
+ VStack(alignment: .leading, spacing: 25) {
+ CopilotIntroItem(
+ imageName: "CopilotLogo",
+ heading: "In-line Code Suggestions",
+ text: "Copilot's code suggestions and text completion now available in Xcode. Just press Tab ⇥ to accept a suggestion."
+ )
+
+ CopilotIntroItem(
+ systemImage: "option",
+ heading: "Full Suggestion",
+ text: "Press Option ⌥ key to display the full suggestion. Only the first line of suggestions are shown inline."
+ )
+
+ CopilotIntroItem(
+ imageName: "GitHubMark",
+ heading: "GitHub Context",
+ text: "Copilot utilizes GitHub and project context to deliver smarter completions and personalized code suggestions relevant to your unique codebase."
+ )
+ }
+
+ Spacer()
+
+ VStack(spacing: 8) {
+ Button(action: { isPresented = false }) {
+ Text("Continue")
+ .padding(.horizontal, 80)
+ .padding(.vertical, 6)
+ }
+ .buttonStyle(.borderedProminent)
+
+ Toggle("Don't show again", isOn: $hideIntro)
+ .toggleStyle(.checkbox)
+ }
+ }
+ .padding(EdgeInsets(top: 50, leading: 50, bottom: 16, trailing: 50))
+ .frame(width: 560, height: 528)
+ }
+ .task {
+ let neverShown = introLastShownVersion.isEmpty
+ isPresented = neverShown || !hideIntro
+ if isPresented {
+ hideIntro = neverShown ? true : hideIntro // default to hidden on first time
+ introLastShownVersion = appVersion
+ }
+ }
+ }
+}
+
+public extension View {
+ func copilotIntroSheet() -> some View {
+ CopilotIntroSheet(content: self)
+ }
+}