diff --git a/ExtensionService/AppDelegate+Menu.swift b/ExtensionService/AppDelegate+Menu.swift deleted file mode 100644 index aa1e9b02..00000000 --- a/ExtensionService/AppDelegate+Menu.swift +++ /dev/null @@ -1,366 +0,0 @@ -import AppKit -import Foundation -import Preferences -import Status -import SuggestionBasic -import XcodeInspector -import Logger -import StatusBarItemView - -extension AppDelegate { - fileprivate var statusBarMenuIdentifier: NSUserInterfaceItemIdentifier { - .init("statusBarMenu") - } - - fileprivate var xcodeInspectorDebugMenuIdentifier: NSUserInterfaceItemIdentifier { - .init("xcodeInspectorDebugMenu") - } - - fileprivate var sourceEditorDebugMenu: NSUserInterfaceItemIdentifier { - .init("sourceEditorDebugMenu") - } - - @MainActor - @objc func buildStatusBarMenu() { - let statusBar = NSStatusBar.system - statusBarItem = statusBar.statusItem( - withLength: NSStatusItem.squareLength - ) - statusBarItem.button?.image = NSImage(named: "MenuBarIcon") - - let statusBarMenu = NSMenu(title: "Status Bar Menu") - statusBarMenu.identifier = statusBarMenuIdentifier - statusBarItem.menu = statusBarMenu - - let boldTitle = NSAttributedString( - string: "Github Copilot", - attributes: [ - .font: NSFont.boldSystemFont(ofSize: NSFont.systemFontSize), - .foregroundColor: NSColor(.primary) - ] - ) - let attributedTitle = NSMenuItem() - attributedTitle.attributedTitle = boldTitle - - let checkForUpdate = NSMenuItem( - title: "Check for Updates", - action: #selector(checkForUpdate), - keyEquivalent: "" - ) - - openCopilotForXcodeItem = NSMenuItem( - title: "Settings", - action: #selector(openCopilotForXcode), - keyEquivalent: "" - ) - - let xcodeInspectorDebug = NSMenuItem( - title: "Xcode Inspector Debug", - action: nil, - keyEquivalent: "" - ) - - let xcodeInspectorDebugMenu = NSMenu(title: "Xcode Inspector Debug") - xcodeInspectorDebugMenu.identifier = xcodeInspectorDebugMenuIdentifier - xcodeInspectorDebug.submenu = xcodeInspectorDebugMenu - xcodeInspectorDebug.isHidden = false - - axStatusItem = NSMenuItem( - title: "", - action: #selector(openExtensionStatusLink), - keyEquivalent: "" - ) - axStatusItem.isHidden = true - - let quitItem = NSMenuItem( - title: "Quit", - action: #selector(quit), - keyEquivalent: "" - ) - quitItem.target = self - - toggleCompletions = NSMenuItem( - title: "Enable/Disable Completions", - action: #selector(toggleCompletionsEnabled), - keyEquivalent: "" - ) - - toggleIgnoreLanguage = NSMenuItem( - title: "No Active Document", - action: nil, - keyEquivalent: "" - ) - - // Auth menu item with custom view - accountItem = NSMenuItem() - accountItem.view = AccountItemView( - target: self, - action: #selector(signIntoGitHub) - ) - - authStatusItem = NSMenuItem( - title: "", - action: nil, - keyEquivalent: "" - ) - axStatusItem.isHidden = true - - upSellItem = NSMenuItem( - title: "", - action: #selector(openUpSellLink), - keyEquivalent: "" - ) - axStatusItem.isHidden = true - - let openDocs = NSMenuItem( - title: "View Documentation", - action: #selector(openCopilotDocs), - keyEquivalent: "" - ) - - let openForum = NSMenuItem( - title: "Feedback Forum", - action: #selector(openCopilotForum), - keyEquivalent: "" - ) - - openChat = NSMenuItem( - title: "Open Chat", - action: #selector(openGlobalChat), - keyEquivalent: "" - ) - - signOutItem = NSMenuItem( - title: "Sign Out", - action: #selector(signOutGitHub), - keyEquivalent: "" - ) - - statusBarMenu.addItem(attributedTitle) - statusBarMenu.addItem(accountItem) - statusBarMenu.addItem(authStatusItem) - statusBarMenu.addItem(upSellItem) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(axStatusItem) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(openCopilotForXcodeItem) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(checkForUpdate) - statusBarMenu.addItem(toggleCompletions) - statusBarMenu.addItem(toggleIgnoreLanguage) - statusBarMenu.addItem(openChat) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(openDocs) - statusBarMenu.addItem(openForum) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(signOutItem) - statusBarMenu.addItem(.separator()) - statusBarMenu.addItem(xcodeInspectorDebug) - statusBarMenu.addItem(quitItem) - - statusBarMenu.delegate = self - xcodeInspectorDebugMenu.delegate = self - } -} - -extension AppDelegate: NSMenuDelegate { - func menuWillOpen(_ menu: NSMenu) { - switch menu.identifier { - case statusBarMenuIdentifier: - if let xcodeInspectorDebug = menu.items.first(where: { item in - item.submenu?.identifier == xcodeInspectorDebugMenuIdentifier - }) { - xcodeInspectorDebug.isHidden = !UserDefaults.shared - .value(for: \.enableXcodeInspectorDebugMenu) - } - - if toggleCompletions != nil { - toggleCompletions.title = "\(UserDefaults.shared.value(for: \.realtimeSuggestionToggle) ? "Disable" : "Enable") Completions" - } - - if toggleIgnoreLanguage != nil { - if let lang = DisabledLanguageList.shared.activeDocumentLanguage { - toggleIgnoreLanguage.title = "\(DisabledLanguageList.shared.isEnabled(lang) ? "Disable" : "Enable") Completions for \(lang.rawValue)" - toggleIgnoreLanguage.action = #selector( - toggleIgnoreLanguageEnabled - ) - } else { - toggleIgnoreLanguage.title = "No Active Document" - toggleIgnoreLanguage.action = nil - } - } - - case xcodeInspectorDebugMenuIdentifier: - let inspector = XcodeInspector.shared - menu.items.removeAll() - menu.items - .append(.text("Active Project: \(inspector.activeProjectRootURL?.path ?? "N/A")")) - menu.items - .append(.text("Active Workspace: \(inspector.activeWorkspaceURL?.path ?? "N/A")")) - menu.items - .append(.text("Active Document: \(inspector.activeDocumentURL?.path ?? "N/A")")) - - if let focusedWindow = inspector.focusedWindow { - menu.items.append(.text( - "Active Window: \(focusedWindow.uiElement.identifier)" - )) - } else { - menu.items.append(.text("Active Window: N/A")) - } - - if let focusedElement = inspector.focusedElement { - menu.items.append(.text( - "Focused Element: \(focusedElement.description)" - )) - } else { - menu.items.append(.text("Focused Element: N/A")) - } - - if let sourceEditor = inspector.focusedEditor { - let label = sourceEditor.element.description - menu.items - .append(.text("Active Source Editor: \(label.isEmpty ? "Unknown" : label)")) - } else { - menu.items.append(.text("Active Source Editor: N/A")) - } - - menu.items.append(.separator()) - - for xcode in inspector.xcodes { - let item = NSMenuItem( - title: "Xcode \(xcode.processIdentifier)", - action: nil, - keyEquivalent: "" - ) - menu.addItem(item) - let xcodeMenu = NSMenu() - item.submenu = xcodeMenu - xcodeMenu.items.append(.text("Is Active: \(xcode.isActive)")) - xcodeMenu.items - .append(.text("Active Project: \(xcode.projectRootURL?.path ?? "N/A")")) - xcodeMenu.items - .append(.text("Active Workspace: \(xcode.workspaceURL?.path ?? "N/A")")) - xcodeMenu.items - .append(.text("Active Document: \(xcode.documentURL?.path ?? "N/A")")) - - for (key, workspace) in xcode.realtimeWorkspaces { - let workspaceItem = NSMenuItem( - title: "Workspace \(key)", - action: nil, - keyEquivalent: "" - ) - xcodeMenu.items.append(workspaceItem) - let workspaceMenu = NSMenu() - workspaceItem.submenu = workspaceMenu - let tabsItem = NSMenuItem( - title: "Tabs", - action: nil, - keyEquivalent: "" - ) - workspaceMenu.addItem(tabsItem) - let tabsMenu = NSMenu() - tabsItem.submenu = tabsMenu - for tab in workspace.tabs { - tabsMenu.addItem(.text(tab)) - } - } - } - - menu.items.append(.separator()) - - menu.items.append(NSMenuItem( - title: "Restart Xcode Inspector", - action: #selector(restartXcodeInspector), - keyEquivalent: "" - )) - - default: - break - } - } -} - -import XPCShared - -private extension AppDelegate { - @objc func restartXcodeInspector() { - Task { - await XcodeInspector.shared.restart(cleanUp: true) - } - } - - @objc func toggleCompletionsEnabled() { - Task { - let initialSetting = UserDefaults.shared.value(for: \.realtimeSuggestionToggle) - do { - let service = getXPCExtensionService() - try await service.toggleRealtimeSuggestion() - } catch { - Logger.service.error("Failed to toggle completions enabled via XPC: \(error)") - UserDefaults.shared.set(!initialSetting, for: \.realtimeSuggestionToggle) - } - } - } - - @objc func toggleIgnoreLanguageEnabled() { - guard let lang = DisabledLanguageList.shared.activeDocumentLanguage else { return } - - if DisabledLanguageList.shared.isEnabled(lang) { - DisabledLanguageList.shared.disable(lang) - } else { - DisabledLanguageList.shared.enable(lang) - } - } - - @objc func openCopilotDocs() { - if let urlString = Bundle.main.object(forInfoDictionaryKey: "COPILOT_DOCS_URL") as? String { - if let url = URL(string: urlString) { - NSWorkspace.shared.open(url) - } - } - } - - @objc func openCopilotForum() { - if let urlString = Bundle.main.object(forInfoDictionaryKey: "COPILOT_FORUM_URL") as? String { - if let url = URL(string: urlString) { - NSWorkspace.shared.open(url) - } - } - } - - @objc func openExtensionStatusLink() { - Task { - let status = await Status.shared.getStatus() - if let s = status.url, let url = URL(string: s) { - NSWorkspace.shared.open(url) - } - } - } - - @objc func openUpSellLink() { - Task { - let status = await Status.shared.getStatus() - if status.authStatus == AuthStatus.Status.notAuthorized { - if let url = URL(string: "https://github.com/features/copilot/plans") { - NSWorkspace.shared.open(url) - } - } else { - if let url = URL(string: "https://github.com/github-copilot/signup/copilot_individual") { - NSWorkspace.shared.open(url) - } - } - } - } -} - -private extension NSMenuItem { - static func text(_ text: String) -> NSMenuItem { - let item = NSMenuItem( - title: text, - action: nil, - keyEquivalent: "" - ) - item.isEnabled = false - return item - } -} diff --git a/ExtensionService/AppDelegate.swift b/ExtensionService/AppDelegate.swift deleted file mode 100644 index 97d44344..00000000 --- a/ExtensionService/AppDelegate.swift +++ /dev/null @@ -1,493 +0,0 @@ -import Combine -import FileChangeChecker -import GitHubCopilotService -import LaunchAgentManager -import Logger -import Preferences -import Service -import ServiceManagement -import Status -import SwiftUI -import UpdateChecker -import UserDefaultsObserver -import UserNotifications -import XcodeInspector -import XPCShared -import GitHubCopilotViewModel -import StatusBarItemView - -let bundleIdentifierBase = Bundle.main - .object(forInfoDictionaryKey: "BUNDLE_IDENTIFIER_BASE") as! String -let serviceIdentifier = bundleIdentifierBase + ".ExtensionService" - -class ExtensionUpdateCheckerDelegate: UpdateCheckerDelegate { - func prepareForRelaunch(finish: @escaping () -> Void) { - Task { - await Service.shared.prepareForExit() - finish() - } - } -} - -@main -class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { - let service = Service.shared - var statusBarItem: NSStatusItem! - var axStatusItem: NSMenuItem! - var openCopilotForXcodeItem: NSMenuItem! - var accountItem: NSMenuItem! - var authStatusItem: NSMenuItem! - var upSellItem: NSMenuItem! - var toggleCompletions: NSMenuItem! - var toggleIgnoreLanguage: NSMenuItem! - var openChat: NSMenuItem! - var signOutItem: NSMenuItem! - var xpcController: XPCController? - let updateChecker = - UpdateChecker( - hostBundle: Bundle(url: locateHostBundleURL(url: Bundle.main.bundleURL)), - checkerDelegate: ExtensionUpdateCheckerDelegate() - ) - var xpcExtensionService: XPCExtensionService? - private var cancellables = Set() - private var progressView: NSProgressIndicator? - - func applicationDidFinishLaunching(_: Notification) { - if ProcessInfo.processInfo.environment["IS_UNIT_TEST"] == "YES" { return } - _ = XcodeInspector.shared - service.start() - AXIsProcessTrustedWithOptions([ - kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true, - ] as CFDictionary) - setupQuitOnUpdate() - setupQuitOnUserTerminated() - xpcController = .init() - Logger.service.info("XPC Service started.") - NSApp.setActivationPolicy(.accessory) - buildStatusBarMenu() - watchServiceStatus() - watchAXStatus() - watchAuthStatus() - setInitialStatusBarStatus() - UserDefaults.shared.set(false, for: \.clsWarningDismissedUntilRelaunch) - } - - @objc func quit() { - Task { @MainActor in - await service.prepareForExit() - await xpcController?.quit() - NSApp.terminate(self) - } - } - - @objc func openCopilotForXcode() { - let task = Process() - let appPath = locateHostBundleURL(url: Bundle.main.bundleURL) - task.launchPath = "/usr/bin/open" - task.arguments = [appPath.absoluteString] - task.launch() - task.waitUntilExit() - } - - @objc func signIntoGitHub() { - Task { @MainActor in - let viewModel = GitHubCopilotViewModel.shared - // Don't trigger the shared viewModel's alert - do { - guard let signInResponse = try await viewModel.preSignIn() else { - return - } - - NSApp.activate(ignoringOtherApps: true) - let alert = NSAlert() - alert.messageText = signInResponse.userCode - alert.informativeText = """ - Please enter the above code in the GitHub website to authorize your \ - GitHub account with Copilot for Xcode. - \(signInResponse.verificationURL.absoluteString) - """ - alert.addButton(withTitle: "Copy Code and Open") - alert.addButton(withTitle: "Cancel") - - let response = alert.runModal() - if response == .alertFirstButtonReturn { - viewModel.signInResponse = signInResponse - viewModel.copyAndOpen() - } - } catch { - Logger.service.error("GitHub copilot view model Sign in fails: \(error)") - } - } - } - - @objc func signOutGitHub() { - Task { @MainActor in - let viewModel = GitHubCopilotViewModel.shared - viewModel.signOut() - } - } - - @objc func openGlobalChat() { - Task { @MainActor in - let serviceGUI = Service.shared.guiController - serviceGUI.openGlobalChat() - } - } - - func setupQuitOnUpdate() { - Task { - guard let url = Bundle.main.executableURL else { return } - let checker = await FileChangeChecker(fileURL: url) - - // If Xcode or Copilot for Xcode is made active, check if the executable of this program - // is changed. If changed, quit this program. - - let sequence = NSWorkspace.shared.notificationCenter - .notifications(named: NSWorkspace.didActivateApplicationNotification) - for await notification in sequence { - try Task.checkCancellation() - guard let app = notification - .userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication, - app.isUserOfService - else { continue } - guard await checker.checkIfChanged() else { - Logger.service.info("Extension Service is not updated, no need to quit.") - continue - } - Logger.service.info("Extension Service will quit.") - #if DEBUG - #else - quit() - #endif - } - } - } - - func setupQuitOnUserTerminated() { - Task { - // Whenever Xcode or the host application quits, check if any of the two is running. - // If none, quit the XPC service. - - let sequence = NSWorkspace.shared.notificationCenter - .notifications(named: NSWorkspace.didTerminateApplicationNotification) - for await notification in sequence { - try Task.checkCancellation() - guard UserDefaults.shared.value(for: \.quitXPCServiceOnXcodeAndAppQuit) - else { continue } - guard let app = notification - .userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication, - app.isUserOfService - else { continue } - if NSWorkspace.shared.runningApplications.contains(where: \.isUserOfService) { - continue - } - quit() - } - } - } - - func requestAccessoryAPIPermission() { - AXIsProcessTrustedWithOptions([ - kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true, - ] as NSDictionary) - } - - @objc func checkForUpdate() { - guard let updateChecker = updateChecker else { - Logger.service.error("Unable to check for updates: updateChecker is nil.") - return - } - updateChecker.checkForUpdates() - } - - func getXPCExtensionService() -> XPCExtensionService { - if let service = xpcExtensionService { return service } - let service = XPCExtensionService(logger: .service) - xpcExtensionService = service - return service - } - - func watchServiceStatus() { - let notifications = NotificationCenter.default.notifications(named: .serviceStatusDidChange) - Task { [weak self] in - for await _ in notifications { - guard let self else { return } - self.updateStatusBarItem() - } - } - } - - func watchAXStatus() { - let osNotifications = DistributedNotificationCenter.default().notifications(named: NSNotification.Name("com.apple.accessibility.api")) - Task { [weak self] in - for await _ in osNotifications { - guard let self else { return } - self.updateStatusBarItem() - } - } - } - - func watchAuthStatus() { - let notifications = DistributedNotificationCenter.default().notifications(named: .authStatusDidChange) - Task { [weak self] in - for await _ in notifications { - guard let self else { return } - await self.forceAuthStatusCheck() - } - } - } - - func setInitialStatusBarStatus() { - Task { - let authStatus = await Status.shared.getAuthStatus() - if authStatus == .unknown { - // temporarily kick off a language server instance to prime the initial auth status - await forceAuthStatusCheck() - } - updateStatusBarItem() - } - } - - func forceAuthStatusCheck() async { - do { - let service = try GitHubCopilotService() - _ = try await service.checkStatus() - try await service.shutdown() - try await service.exit() - } catch { - Logger.service.error("Failed to read auth status: \(error)") - } - } - - private func configureNotLoggedIn() { - self.accountItem.view = AccountItemView( - target: self, - action: #selector(signIntoGitHub) - ) - self.authStatusItem.isHidden = true - self.upSellItem.isHidden = true - self.toggleCompletions.isHidden = true - self.toggleIgnoreLanguage.isHidden = true - self.openChat.isHidden = true - self.signOutItem.isHidden = true - } - - private func configureLoggedIn(status: StatusResponse) { - self.accountItem.view = AccountItemView( - target: self, - action: nil, - userName: status.userName ?? "" - ) - if !status.clsMessage.isEmpty { - self.authStatusItem.isHidden = false - let CLSMessageSummary = getCLSMessageSummary(status.clsMessage) - self.authStatusItem.title = CLSMessageSummary.summary - - let submenu = NSMenu() - let attributedCLSErrorItem = NSMenuItem() - attributedCLSErrorItem.view = ErrorMessageView( - errorMessage: CLSMessageSummary.detail - ) - submenu.addItem(attributedCLSErrorItem) - submenu.addItem(.separator()) - submenu.addItem( - NSMenuItem( - title: "View Details on GitHub", - action: #selector(openGitHubDetailsLink), - keyEquivalent: "" - ) - ) - - self.authStatusItem.submenu = submenu - self.authStatusItem.isEnabled = true - - self.upSellItem.title = "Upgrade Now" - self.upSellItem.isHidden = false - self.upSellItem.isEnabled = true - } else { - self.authStatusItem.isHidden = true - self.upSellItem.isHidden = true - } - self.toggleCompletions.isHidden = false - self.toggleIgnoreLanguage.isHidden = false - self.openChat.isHidden = false - self.signOutItem.isHidden = false - } - - private func configureNotAuthorized(status: StatusResponse) { - self.accountItem.view = AccountItemView( - target: self, - action: nil, - userName: status.userName ?? "" - ) - self.authStatusItem.isHidden = false - self.authStatusItem.title = "No Subscription" - - let submenu = NSMenu() - let attributedNotAuthorizedItem = NSMenuItem() - attributedNotAuthorizedItem.view = ErrorMessageView( - errorMessage: "GitHub Copilot features are disabled. Check your subscription to enable them." - ) - attributedNotAuthorizedItem.isEnabled = true - submenu.addItem(attributedNotAuthorizedItem) - - self.authStatusItem.submenu = submenu - self.authStatusItem.isEnabled = true - - self.upSellItem.title = "Check Subscription Plans" - self.upSellItem.isHidden = false - self.upSellItem.isEnabled = true - self.toggleCompletions.isHidden = true - self.toggleIgnoreLanguage.isHidden = true - self.openChat.isHidden = true - self.signOutItem.isHidden = false - } - - private func configureUnknown() { - self.accountItem.view = AccountItemView( - target: self, - action: nil, - userName: "Unknown User" - ) - self.authStatusItem.isHidden = true - self.upSellItem.isHidden = true - self.toggleCompletions.isHidden = false - self.toggleIgnoreLanguage.isHidden = false - self.openChat.isHidden = false - self.signOutItem.isHidden = false - } - - func updateStatusBarItem() { - Task { @MainActor in - let status = await Status.shared.getStatus() - /// Update status bar icon - self.statusBarItem.button?.image = status.icon.nsImage - - /// Update auth status related status bar items - switch status.authStatus { - case .notLoggedIn: configureNotLoggedIn() - case .loggedIn: configureLoggedIn(status: status) - case .notAuthorized: configureNotAuthorized(status: status) - case .unknown: configureUnknown() - } - - /// Update accessibility permission status bar item - if let message = status.message { - self.axStatusItem.title = message - if let image = NSImage( - systemSymbolName: "exclamationmark.circle.fill", - accessibilityDescription: "Accessibility permission not granted" - ) { - image.isTemplate = false - image.withSymbolConfiguration(.init(paletteColors: [.red])) - self.axStatusItem.image = image - } - self.axStatusItem.isHidden = false - self.axStatusItem.isEnabled = status.url != nil - } else { - self.axStatusItem.isHidden = true - } - - /// Update settings status bar item - if status.extensionStatus == .failed { - if let image = NSImage( - systemSymbolName: "exclamationmark.circle.fill", - accessibilityDescription: "Extension permission not granted" - ) { - image.isTemplate = false - image.withSymbolConfiguration(.init(paletteColors: [.red])) - self.openCopilotForXcodeItem.image = image - } - } else { - self.openCopilotForXcodeItem.image = nil - } - self.markAsProcessing(status.inProgress) - } - } - - func markAsProcessing(_ isProcessing: Bool) { - if !isProcessing { - // No longer in progress - progressView?.removeFromSuperview() - progressView = nil - return - } - if progressView != nil { - // Already in progress - return - } - let progress = NSProgressIndicator() - progress.style = .spinning - progress.sizeToFit() - progress.frame = statusBarItem.button?.bounds ?? .zero - progress.isIndeterminate = true - progress.startAnimation(nil) - statusBarItem.button?.addSubview(progress) - statusBarItem.button?.image = nil - progressView = progress - } - - @objc func openGitHubDetailsLink() { - Task { - if let url = URL(string: "https://github.com/copilot") { - NSWorkspace.shared.open(url) - } - } - } -} - -extension NSRunningApplication { - var isUserOfService: Bool { - [ - "com.apple.dt.Xcode", - bundleIdentifierBase, - ].contains(bundleIdentifier) - } -} - -func locateHostBundleURL(url: URL) -> URL { - var nextURL = url - while nextURL.path != "/" { - nextURL = nextURL.deletingLastPathComponent() - if nextURL.lastPathComponent.hasSuffix(".app") { - return nextURL - } - } - let devAppURL = url - .deletingLastPathComponent() - .appendingPathComponent("GitHub Copilot for Xcode Dev.app") - return devAppURL -} - -struct CLSMessage { - let summary: String - let detail: String -} - -func extractDateFromCLSMessage(_ message: String) -> String? { - let pattern = #"until (\d{1,2}/\d{1,2}/\d{4}, \d{1,2}:\d{2}:\d{2} [AP]M)"# - if let range = message.range(of: pattern, options: .regularExpression) { - return String(message[range].dropFirst(6)) - } - return nil -} - -func getCLSMessageSummary(_ message: String) -> CLSMessage { - let summary: String - if message.contains("You've reached your monthly chat messages limit") { - summary = "Monthly Chat Limit Reached" - } else if message.contains("You've reached your monthly code completion limit") { - summary = "Monthly Completion Limit Reached" - } else { - summary = "CLS Error" - } - - let detail: String - if let date = extractDateFromCLSMessage(message) { - detail = "Visit GitHub to check your usage and upgrade to Copilot Pro or wait until \(date) for your limit to reset." - } else { - detail = message - } - - return CLSMessage(summary: summary, detail: detail) -} diff --git a/ExtensionService/Assets.xcassets/AccentColor.colorset/Contents.json b/ExtensionService/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb878970..00000000 --- a/ExtensionService/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/Contents.json b/ExtensionService/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 0a30d46d..00000000 --- a/ExtensionService/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images" : [ - { - "filename" : "CopilotforXcode-Icon@16w_1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "filename" : "CopilotforXcode-Icon@16w_2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "filename" : "CopilotforXcode-Icon@32w_1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "filename" : "CopilotforXcode-Icon@32w_2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "filename" : "CopilotforXcode-Icon@128w_1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "filename" : "CopilotforXcode-Icon@128w_2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "filename" : "CopilotforXcode-Icon@256w_1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "filename" : "CopilotforXcode-Icon@256w_2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "filename" : "CopilotforXcode-Icon@512w_1x.png", - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "filename" : "CopilotforXcode-Icon@512w_2x.png", - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_1x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_1x.png deleted file mode 100644 index 3ee52427..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_1x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_2x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_2x.png deleted file mode 100644 index 88b20d1d..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@128w_2x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_1x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_1x.png deleted file mode 100644 index 2bb554dc..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_1x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_2x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_2x.png deleted file mode 100644 index ce02bac7..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@16w_2x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_1x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_1x.png deleted file mode 100644 index 7674f663..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_1x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_2x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_2x.png deleted file mode 100644 index fc705969..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@256w_2x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_1x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_1x.png deleted file mode 100644 index ce02bac7..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_1x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_2x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_2x.png deleted file mode 100644 index 4d52c81b..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@32w_2x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_1x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_1x.png deleted file mode 100644 index fc705969..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_1x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_2x.png b/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_2x.png deleted file mode 100644 index 54da6e3f..00000000 Binary files a/ExtensionService/Assets.xcassets/AppIcon.appiconset/CopilotforXcode-Icon@512w_2x.png and /dev/null differ diff --git a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/Contents.json deleted file mode 100644 index c48d2889..00000000 --- a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "light1x.svg", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "dark1x.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/dark1x.svg b/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/dark1x.svg deleted file mode 100644 index b0e60fbf..00000000 --- a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/dark1x.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/light1x.svg b/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/light1x.svg deleted file mode 100644 index 1f52da33..00000000 --- a/ExtensionService/Assets.xcassets/CodeBlockInsertIcon.imageset/light1x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ExtensionService/Assets.xcassets/Contents.json b/ExtensionService/Assets.xcassets/Contents.json deleted file mode 100644 index 73c00596..00000000 --- a/ExtensionService/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ExtensionService/Assets.xcassets/CopilotLogo.imageset/Contents.json b/ExtensionService/Assets.xcassets/CopilotLogo.imageset/Contents.json deleted file mode 100644 index 2e35661e..00000000 --- a/ExtensionService/Assets.xcassets/CopilotLogo.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "copilot.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/ExtensionService/Assets.xcassets/CopilotLogo.imageset/copilot.svg b/ExtensionService/Assets.xcassets/CopilotLogo.imageset/copilot.svg deleted file mode 100644 index 8284dce7..00000000 --- a/ExtensionService/Assets.xcassets/CopilotLogo.imageset/copilot.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/ExtensionService/Assets.xcassets/HistoryIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/HistoryIcon.imageset/Contents.json deleted file mode 100644 index 83896fae..00000000 --- a/ExtensionService/Assets.xcassets/HistoryIcon.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "history.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/ExtensionService/Assets.xcassets/HistoryIcon.imageset/history.svg b/ExtensionService/Assets.xcassets/HistoryIcon.imageset/history.svg deleted file mode 100644 index 37462496..00000000 --- a/ExtensionService/Assets.xcassets/HistoryIcon.imageset/history.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ExtensionService/Assets.xcassets/ItemSelectedColor.colorset/Contents.json b/ExtensionService/Assets.xcassets/ItemSelectedColor.colorset/Contents.json deleted file mode 100644 index 955c4738..00000000 --- a/ExtensionService/Assets.xcassets/ItemSelectedColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "248", - "green" : "154", - "red" : "98" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "194", - "green" : "108", - "red" : "55" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Contents.json deleted file mode 100644 index 4ab2faba..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "Status=active, Mode=dark.svg", - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "filename" : "Status=active, Mode=white.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=dark.svg b/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=dark.svg deleted file mode 100644 index 7e472bde..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=white.svg b/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=white.svg deleted file mode 100644 index 22dd8c1a..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarIcon.imageset/Status=active, Mode=white.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Contents.json deleted file mode 100644 index 4829284b..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "Status=inactive, Mode=dark.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Status=inactive, Mode=dark.svg b/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Status=inactive, Mode=dark.svg deleted file mode 100644 index 58b44f03..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarInactiveIcon.imageset/Status=inactive, Mode=dark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Contents.json deleted file mode 100644 index 4ebbfc18..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "Status=error, Mode=dark.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Status=error, Mode=dark.svg b/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Status=error, Mode=dark.svg deleted file mode 100644 index d3263f54..00000000 --- a/ExtensionService/Assets.xcassets/MenuBarWarningIcon.imageset/Status=error, Mode=dark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Contents.json b/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Contents.json deleted file mode 100644 index c4b93a1c..00000000 --- a/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "Xcode_16x16.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Xcode_16x16.svg b/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Xcode_16x16.svg deleted file mode 100644 index 0e118ea5..00000000 --- a/ExtensionService/Assets.xcassets/XcodeIcon.imageset/Xcode_16x16.svg +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ExtensionService/ExtensionService.entitlements b/ExtensionService/ExtensionService.entitlements deleted file mode 100644 index 3c568976..00000000 --- a/ExtensionService/ExtensionService.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.application-groups - - $(TeamIdentifierPrefix)group.$(BUNDLE_IDENTIFIER_BASE) - - com.apple.security.cs.disable-library-validation - - - diff --git a/ExtensionService/Info.plist b/ExtensionService/Info.plist deleted file mode 100644 index 19f114ff..00000000 --- a/ExtensionService/Info.plist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - APPLICATION_SUPPORT_FOLDER - $(APPLICATION_SUPPORT_FOLDER) - APP_ID_PREFIX - $(AppIdentifierPrefix) - BUNDLE_IDENTIFIER_BASE - $(BUNDLE_IDENTIFIER_BASE) - EXTENSION_BUNDLE_NAME - $(EXTENSION_BUNDLE_NAME) - HOST_APP_NAME - $(HOST_APP_NAME) - LANGUAGE_SERVER_PATH - $(LANGUAGE_SERVER_PATH) - NODE_PATH - $(NODE_PATH) - TEAM_ID_PREFIX - $(TeamIdentifierPrefix) - XPCService - - ServiceType - Application - - COPILOT_DOCS_URL - $(COPILOT_DOCS_URL) - COPILOT_FORUM_URL - $(COPILOT_FORUM_URL) - STANDARD_TELEMETRY_CHANNEL_KEY - $(STANDARD_TELEMETRY_CHANNEL_KEY) - - diff --git a/ExtensionService/Main.storyboard b/ExtensionService/Main.storyboard deleted file mode 100644 index 5fc73e7e..00000000 --- a/ExtensionService/Main.storyboard +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - Default - - - - - - - Left to Right - - - - - - - Right to Left - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ExtensionService/ServiceDelegate.swift b/ExtensionService/ServiceDelegate.swift deleted file mode 100644 index 6280582f..00000000 --- a/ExtensionService/ServiceDelegate.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import Service -import XPCShared - -class ServiceDelegate: NSObject, NSXPCListenerDelegate { - func listener( - _: NSXPCListener, - shouldAcceptNewConnection newConnection: NSXPCConnection - ) -> Bool { - newConnection.exportedInterface = NSXPCInterface( - with: XPCServiceProtocol.self - ) - - let exportedObject = XPCService() - newConnection.exportedObject = exportedObject - newConnection.resume() - return true - } -} - diff --git a/ExtensionService/XPCController.swift b/ExtensionService/XPCController.swift deleted file mode 100644 index 5fdd4445..00000000 --- a/ExtensionService/XPCController.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import Logger -import XPCShared - -final class XPCController: XPCServiceDelegate { - let bridge: XPCCommunicationBridge - let xpcListener: NSXPCListener - let xpcServiceDelegate: ServiceDelegate - - var pingTask: Task? - - init() { - let bridge = XPCCommunicationBridge(logger: .client) - let listener = NSXPCListener.anonymous() - let delegate = ServiceDelegate() - listener.delegate = delegate - listener.resume() - xpcListener = listener - xpcServiceDelegate = delegate - self.bridge = bridge - - Task { - bridge.setDelegate(self) - createPingTask() - } - } - - func quit() async { - bridge.setDelegate(nil) - pingTask?.cancel() - try? await bridge.quit() - } - - deinit { - xpcListener.invalidate() - pingTask?.cancel() - } - - func createPingTask() { - pingTask?.cancel() - pingTask = Task { [weak self] in - while !Task.isCancelled { - guard let self else { return } - do { - try await self.bridge.updateServiceEndpoint(self.xpcListener.endpoint) - try await Task.sleep(nanoseconds: 60_000_000_000) - } catch { - try await Task.sleep(nanoseconds: 1_000_000_000) - #if DEBUG - // No log, but you should run CommunicationBridge, too. - #else - Logger.service - .error("Failed to connect to bridge: \(error.localizedDescription)") - #endif - } - } - } - } - - func connectionDidInvalidate() async { - // ignore - } - - func connectionDidInterrupt() async { - createPingTask() // restart the ping task so that it can bring the bridge back immediately. - } -} - diff --git a/README.md b/README.md deleted file mode 100644 index 791a9227..00000000 --- a/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# GitHub Copilot for Xcode - -[GitHub Copilot](https://github.com/features/copilot) is an AI pair programmer -tool that helps you write code faster and smarter. Copilot for Xcode is an Xcode extension that provides inline coding suggestions as you type and a chat assistant to answer your coding questions. - -## Chat [Preview] - -GitHub Copilot Chat provides suggestions to your specific coding tasks via chat. -Chat of GitHub Copilot for Xcode - -## Code Completion - -You can receive auto-complete type suggestions from GitHub Copilot either by starting to write the code you want to use, or by writing a natural language comment describing what you want the code to do. -Code Completion of GitHub Copilot for Xcode - -## Preview Policy - -Use of the GitHub Copilot Xcode Extension is subject to [GitHub's Pre-Release Terms](https://docs.github.com/en/site-policy/github-terms/github-pre-release-license-terms). We want to remind you that: - -> Previews may not be supported or may change at any time. You may receive confidential information through those programs that must remain confidential while the program is private. We'd love your feedback to make our Previews better. - -## Requirements - -- macOS 12+ -- Xcode 8+ -- A GitHub Copilot subscription. To learn more, visit [https://github.com/features/copilot](https://github.com/features/copilot). - -## Getting Started - -1. Install via [Homebrew](https://brew.sh/): - - ```sh - brew install --cask github-copilot-for-xcode - ``` - - Or download the `dmg` from - [the latest release](https://github.com/github/CopilotForXcode/releases/latest/download/GitHubCopilotForXcode.dmg). - Drag `GitHub Copilot for Xcode` into the `Applications` folder: - -

- Screenshot of opened dmg -

- - Updates can be downloaded and installed by the app. - -1. Open the `GitHub Copilot for Xcode` application (from the `Applications` folder). Accept the security warning. -

- Screenshot of MacOS download permission request -

- - -1. A background item will be added to enable Copilot to start when `GitHub Copilot for Xcode` is opened. -

- Screenshot of background item -

- -1. Two permissions are required: `Accessibility` and `Xcode Source Editor - Extension`. For more on why these permissions are required see - [TROUBLESHOOTING.md](./TROUBLESHOOTING.md). - - The first time the application is run the `Accessibility` permission should be requested: - -

- Screenshot of accessibility permission request -

- - The `Xcode Source Editor Extension` permission needs to be enabled manually. Click - `Extension Permission` from the `GitHub Copilot for Xcode` application settings to open the - System Preferences to the `Extensions` panel. Select `Xcode Source Editor` - and enable `GitHub Copilot`: - -

- Screenshot of extension permission -

- -1. After granting the extension permission, open Xcode. Verify that the - `Github Copilot` menu is available and enabled under the Xcode `Editor` - menu. -
-

- Screenshot of Xcode Editor GitHub Copilot menu item -

- - Keyboard shortcuts can be set for all menu items in the `Key Bindings` - section of Xcode preferences. - -1. To sign into GitHub Copilot, click the `Sign in` button in the settings application. This will open a browser window and copy a code to the clipboard. Paste the code into the GitHub login page and authorize the application. -

- Screenshot of sign-in popup -

- -1. To install updates, click `Check for Updates` from the menu item or in the - settings application. - - After installing a new version, Xcode must be restarted to use the new - version correctly. - - New versions can also be installed from `dmg` files downloaded from the - releases page. When installing a new version via `dmg`, the application must - be run manually the first time to accept the downloaded from the internet - warning. - -1. To avoid confusion, we recommend disabling `Predictive code completion` under - `Xcode` > `Preferences` > `Text Editing` > `Editing`. - -1. Press `tab` to accept the first line of a suggestion, hold `option` to view - the full suggestion, and press `option` + `tab` to accept the full suggestion. - -

- Screenshot of welcome screen -

- -## How to use Chat [Preview] - - Open Copilot Chat in GitHub Copilot. - - Open via the Xcode menu `Xcode -> Editor -> GitHub Copilot -> Open Chat`. -

- Screenshot of Xcode Editor GitHub Copilot menu item -

- - - Open via GitHub Copilot app menu `Open Chat`. - -

- Screenshot of GitHub Copilot menu item -

- -## How to use Code Completion - - Press `tab` to accept the first line of a suggestion, hold `option` to view - the full suggestion, and press `option` + `tab` to accept the full suggestion. - -## License - -This project is licensed under the terms of the MIT open source license. Please -refer to [LICENSE.txt](./LICENSE.txt) for the full terms. - -## Privacy - -We follow responsible practices in accordance with our -[Privacy Statement](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement). - -To get the latest security fixes, please use the latest version of the GitHub -Copilot for Xcode. - -## Support - -We’d love to get your help in making GitHub Copilot better! If you have -feedback or encounter any problems, please reach out on our [Feedback -forum](https://github.com/orgs/community/discussions/categories/copilot). - -## Acknowledgements - -Thank you to @intitni for creating the original project that this is based on. - -Attributions can be found under About when running the app or in -[Credits.rtf](./Copilot%20for%20Xcode/Credits.rtf). \ No newline at end of file