diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2e8b1912 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing + +Thanks for helping improve **GitHub Copilot for Xcode**. + +## Before you start + +- Read [README.md](./README.md) for product-level setup. +- Read [DEVELOPMENT.md](./DEVELOPMENT.md) for architecture notes and local build details. +- Use the existing documentation and test patterns already present in the repository. + +## Development workflow + +1. Install the dependencies required by the Xcode workspace, including Node and `npm` as described in [DEVELOPMENT.md](./DEVELOPMENT.md). +2. Open `Copilot for Xcode.xcworkspace` in Xcode. +3. Build the `Copilot for Xcode` scheme or use `Script/localbuild-app.sh` for a local archive. +4. When changing extension behavior, test the app, `ExtensionService`, `CommunicationBridge`, and `EditorExtension` targets together. + +## Testing + +- Run the relevant Xcode tests before sending a change. +- Add new tests to `TestPlan.xctestplan` when you create a new unit test target entry. +- Keep changes small and validate only the areas you touched when possible. + +## Documentation expectations + +Update documentation when you change: + +- installation or permission flows +- authentication or BYOK setup +- Xcode menu locations or feature behavior +- development or testing procedures + +## Code style + +- Follow the existing Swift style in the repository. +- Use SwiftFormat settings from `.swiftformat`. +- Prefer focused doc comments for public or cross-module types when behavior is not obvious. + +## Security and secrets + +- Never commit API keys, provider tokens, or other credentials. +- Keep BYOK secrets in the app configuration flow only. +- Review changes that touch permissions, auth, or file access carefully. + +## Pull request checklist + +- Build or test the affected targets. +- Update docs if the user or developer workflow changed. +- Keep the scope minimal and avoid unrelated refactors. +- Include troubleshooting details when fixing Xcode-specific behavior. diff --git a/Core/Sources/ChatService/ChatService.swift b/Core/Sources/ChatService/ChatService.swift index ac75d819..cd264300 100644 --- a/Core/Sources/ChatService/ChatService.swift +++ b/Core/Sources/ChatService/ChatService.swift @@ -19,6 +19,7 @@ import GitHelper import LanguageServerProtocol import SuggestionBasic +/// Common chat operations shared by workspace tabs and standalone chat surfaces. public protocol ChatServiceType { var memory: ContextAwareAutoManagedChatMemory { get set } func send( @@ -41,6 +42,7 @@ public protocol ChatServiceType { func copyCode(_ id: String) async } +/// Tracks a pending client-side tool call so the service can route the eventual response back correctly. struct ToolCallRequest { let requestId: JSONId let turnId: String @@ -49,6 +51,7 @@ struct ToolCallRequest { let completion: (AnyJSONRPCResponse) -> Void } +/// Stores parent-child relationships between turns created by subagents or tool confirmation flows. struct ConversationTurnTrackingState { var turnParentMap: [String: String] = [:] // Maps subturn ID to parent turn ID var validConversationIds: Set = [] // Tracks all valid conversation IDs including subagents @@ -59,8 +62,9 @@ struct ConversationTurnTrackingState { } } +/// Owns chat history, Copilot conversation state, and tool-call coordination for a single chat tab. public final class ChatService: ChatServiceType, ObservableObject { - + public var memory: ContextAwareAutoManagedChatMemory @Published public internal(set) var chatHistory: [ChatMessage] = [] @Published public internal(set) var isReceivingMessage = false @@ -115,6 +119,7 @@ public final class ChatService: ChatServiceType, ObservableObject { chatTabInfo.isSelected = tabInfo.isSelected } + /// Keeps published chat history in sync with the backing memory store and progress events. private func subscribeToNotifications() { memory.observeHistoryChange { [weak self] in Task { [weak self] in @@ -136,6 +141,7 @@ public final class ChatService: ChatServiceType, ObservableObject { }.store(in: &cancellables) } + /// Listens for context lookups so Copilot requests can pull in editor and workspace state on demand. private func subscribeToConversationContextRequest() { self.conversationContextHandler.onConversationContext.sink(receiveValue: { [weak self] (request, completion) in guard let skills = self?.skillSet, !skills.isEmpty, request.params!.conversationId == self?.conversationId else { return } diff --git a/Core/Sources/Service/Service.swift b/Core/Sources/Service/Service.swift index ab6c35e2..5f3b48a2 100644 --- a/Core/Sources/Service/Service.swift +++ b/Core/Sources/Service/Service.swift @@ -18,6 +18,7 @@ import ChatService import Persist import PersistMiddleware +/// Serializes work that must stay coordinated with the long-lived extension service. @globalActor public enum ServiceActor { public actor TheActor {} public static let shared = TheActor() @@ -96,6 +97,7 @@ public final class Service { Logger.telemetryLogger = TelemetryLogger() } + /// Starts the host-side controllers that power the menu bar UI, suggestions, and workspace sync. @MainActor public func start() { scheduledCleaner.start() @@ -161,6 +163,7 @@ public final class Service { } } + /// Stops long-running integrations so the app can exit without leaving helper processes behind. @MainActor public func prepareForExit() async { Logger.service.info("Prepare for exit.") @@ -193,7 +196,7 @@ public extension Service { // internal extension extension Service { - + /// Switches the active chat workspace after both the workspace URL and auth state are known. func onNewActiveWorkspaceURLOrAuthStatus(newURL: URL?, newStatus: AuthStatus) { Task { @MainActor in // check path @@ -212,10 +215,11 @@ extension Service { } } + /// Updates chat state to the selected workspace and restores any persisted conversation data. + /// /// - Parameters: - /// - workspaceURL: The active workspace URL that need switch to - /// - path: Path of the workspace URL - /// - username: Curent github username + /// - workspaceURL: The active workspace URL to switch to. + /// - username: The current signed-in GitHub username. @MainActor func doSwitchWorkspace(workspaceURL: URL, username: String) async { // get workspace display name diff --git a/Core/Sources/Service/XPCService.swift b/Core/Sources/Service/XPCService.swift index b64e841c..4d7e5a45 100644 --- a/Core/Sources/Service/XPCService.swift +++ b/Core/Sources/Service/XPCService.swift @@ -12,6 +12,7 @@ import GitHubCopilotViewModel import Workspace import ConversationServiceProvider +/// Bridges Xcode extension requests into the long-running service process. public class XPCService: NSObject, XPCServiceProtocol { // MARK: - Service @@ -51,6 +52,7 @@ public class XPCService: NSObject, XPCServiceProtocol { // MARK: - Suggestion + /// Decodes editor state, executes a suggestion command, and replies with the updated buffer content. @discardableResult private func replyWithUpdatedContent( editorContent: Data, diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 5e7a287b..1239c4bc 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -62,6 +62,17 @@ To build the application locally, follow these steps: 2. After successful build, the application will be available in the build directory. Copy `GitHub Copilot for Xcode.app` to your Applications folder to test it locally. +## Debugging the Local Language Server + +To debug against a local checkout of the Visual Studio Code Copilot language server, create `/home/runner/work/CopilotForXcode/CopilotForXcode/Config.local.xcconfig` and set: + +```xcconfig +LANGUAGE_SERVER_PATH = /absolute/path/to/your/copilot-language-server/repo +NODE_PATH = /absolute/path/to/node +``` + +`NODE_PATH` is optional. When it is omitted, debug builds now fall back to `node` from your shell `PATH`. + ## SwiftUI Previews Looks like SwiftUI Previews are not very happy with Objective-C packages when running with app targets. To use previews, please switch schemes to the package product targets. diff --git a/README.md b/README.md index eea0b39a..6df2c9cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,19 @@ # GitHub Copilot for Xcode +[![Build status](https://github.com/Pjrich1313/CopilotForXcode/actions/workflows/codeql.yml/badge.svg)](https://github.com/Pjrich1313/CopilotForXcode/actions/workflows/codeql.yml) +[![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-Repository-blue?logo=githubactions&logoColor=white)](https://github.com/Pjrich1313/CopilotForXcode/actions) +[![Version](https://img.shields.io/github/v/release/Pjrich1313/CopilotForXcode?display_name=tag)](https://github.com/Pjrich1313/CopilotForXcode/releases) + [GitHub Copilot](https://github.com/features/copilot) for Xcode is the leading AI coding assistant for Swift, Objective-C and iOS/macOS development. It delivers intelligent Completions, Chat, and Code Review—plus advanced features like Agent Mode, Next Edit Suggestions, MCP Registry, and Copilot Vision to make Xcode development faster and smarter. +## Quick Start + +1. Install the app with [Homebrew](https://brew.sh/) or from the latest DMG by following the [installation guide](./docs/INSTALLATION.md). +2. Launch `GitHub Copilot for Xcode`, then grant the required `Background`, `Accessibility`, and `Xcode Source Editor Extension` permissions. +3. Sign in with the GitHub account that has your GitHub Copilot access, then complete any optional BYOK provider setup in [configuration](./docs/CONFIGURATION.md). +4. Open Xcode and use `Editor > GitHub Copilot` to start Chat, Agent Mode, Code Review, or inline completions. +5. Keep the [feature guide](./docs/FEATURES.md), [examples](./docs/EXAMPLES.md), and [troubleshooting guide](./TROUBLESHOOTING.md) nearby while you get set up. + ## Chat GitHub Copilot Chat provides suggestions to your specific coding tasks via chat. @@ -27,8 +39,20 @@ You can receive auto-complete type suggestions from GitHub Copilot either by sta ## Requirements - macOS 12+ -- Xcode 8+ -- A GitHub account +- A version of Xcode that supports Source Editor Extensions +- A GitHub account with GitHub Copilot access, or provider credentials for BYOK model usage + +## Documentation + +- [Installation guide](./docs/INSTALLATION.md) +- [Configuration and authentication](./docs/CONFIGURATION.md) +- [Feature guide](./docs/FEATURES.md) +- [Common Xcode scenarios and examples](./docs/EXAMPLES.md) +- [Bring Your Own Key (BYOK)](./Docs/BYOK.md) +- [Custom instructions](./Docs/CustomInstructions.md) +- [Prompt files](./Docs/PromptFiles.md) +- [Troubleshooting](./TROUBLESHOOTING.md) +- [Contributing](./CONTRIBUTING.md) ## Getting Started @@ -87,7 +111,7 @@ You can receive auto-complete type suggestions from GitHub Copilot either by sta 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. +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, authorize the application, and complete the flow with the GitHub account that has your Copilot access.

Screenshot of sign-in popup

diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift index 978c6e92..c15bde46 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift @@ -172,6 +172,19 @@ public extension Notification.Name { .Name("com.github.CopilotForXcode.GithubCopilotAgentTrustToolAnnotationsDidChange") } +private func resolvedInfoDictionaryString(_ key: String) -> String? { + guard let value = Bundle.main.infoDictionary?[key] as? String else { + return nil + } + + let trimmedValue = value.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedValue.isEmpty, !(trimmedValue.hasPrefix("$(") && trimmedValue.hasSuffix(")")) else { + return nil + } + + return trimmedValue +} + public class GitHubCopilotBaseService { let projectRootURL: URL var server: GitHubCopilotLSP @@ -219,11 +232,11 @@ public class GitHubCopilotBaseService { #if DEBUG // Use local language server if set and available - if let languageServerPath = Bundle.main.infoDictionary?["LANGUAGE_SERVER_PATH"] as? String { + if let languageServerPath = resolvedInfoDictionaryString("LANGUAGE_SERVER_PATH") { let jsPath = URL(fileURLWithPath: NSString(string: languageServerPath).expandingTildeInPath) .appendingPathComponent("dist") .appendingPathComponent("language-server.js") - let nodePath = Bundle.main.infoDictionary?["NODE_PATH"] as? String ?? "node" + let nodePath = resolvedInfoDictionaryString("NODE_PATH") ?? "node" if FileManager.default.fileExists(atPath: jsPath.path) { path = "/usr/bin/env" if projectRootURL.path == "/" { diff --git a/Tool/Sources/Workspace/WorkspacePool.swift b/Tool/Sources/Workspace/WorkspacePool.swift index 44468e07..23dcfc51 100644 --- a/Tool/Sources/Workspace/WorkspacePool.swift +++ b/Tool/Sources/Workspace/WorkspacePool.swift @@ -14,11 +14,13 @@ public extension DependencyValues { } } +/// Serializes workspace creation and mutation so file-backed state stays consistent across Xcode events. @globalActor public enum WorkspaceActor { public actor TheActor {} public static let shared = TheActor() } +/// Tracks open workspaces and attaches per-workspace plugins used by suggestions and chat features. public class WorkspacePool { public enum Error: Swift.Error, LocalizedError { case invalidWorkspaceURL(URL) @@ -42,6 +44,7 @@ public class WorkspacePool { self.plugins = plugins } + /// Registers a workspace plugin and attaches it to both current and future workspaces. public func registerPlugin(_ plugin: @escaping (Workspace) -> Plugin) { let id = ObjectIdentifier(Plugin.self) let erasedPlugin: (Workspace) -> WorkspacePlugin = { plugin($0) } @@ -87,6 +90,7 @@ public class WorkspacePool { return workspace.flatMap { ws in filespace.map { fs in (ws, fs) } } } + /// Returns the existing workspace for a URL or creates a new one when Xcode opens a project for the first time. @WorkspaceActor public func fetchOrCreateWorkspace(workspaceURL: URL) async throws -> Workspace { guard workspaceURL != URL(fileURLWithPath: "/") else { @@ -102,6 +106,7 @@ public class WorkspacePool { return new } + /// Resolves the active workspace/filespace pair for an editor file, creating missing state as needed. @WorkspaceActor public func fetchOrCreateWorkspaceAndFilespace(fileURL: URL) async throws -> (workspace: Workspace, filespace: Filespace) @@ -187,4 +192,3 @@ extension WorkspacePool { return new } } - diff --git a/Tool/Sources/XPCShared/XPCServiceProtocol.swift b/Tool/Sources/XPCShared/XPCServiceProtocol.swift index 4489233f..06c2a1e5 100644 --- a/Tool/Sources/XPCShared/XPCServiceProtocol.swift +++ b/Tool/Sources/XPCShared/XPCServiceProtocol.swift @@ -3,6 +3,7 @@ import Status import SuggestionBasic @objc(XPCServiceProtocol) +/// Public XPC surface used by the editor extension and host app to communicate with the service process. public protocol XPCServiceProtocol { func getSuggestedCode(editorContent: Data, withReply reply: @escaping (_ updatedContent: Data?, Error?) -> Void) func getNextSuggestedCode(editorContent: Data, withReply reply: @escaping (_ updatedContent: Data?, Error?) -> Void) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 00000000..3ff84e67 --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,81 @@ +# Configuration + +This guide covers the most common configuration tasks after installing **GitHub Copilot for Xcode**. + +## Authentication + +### GitHub Copilot account + +1. Open the `GitHub Copilot for Xcode` app. +2. Click `Sign in`. +3. Finish the browser-based device authorization flow with the GitHub account that has Copilot access. +4. Confirm the app shows the signed-in state before opening Xcode workflows that depend on chat, code review, or agent mode. + +### Sign out + +Use the app settings to sign out if you need to switch to a different Copilot-enabled account. + +## Permissions + +Copilot for Xcode works best when these permissions are granted: + +- `Background` for the host app and communication bridge +- `Accessibility` for realtime editor context and accepting suggestions +- `Xcode Source Editor Extension` for the Editor menu integration +- `Files & Folders` for workspace-aware chat and agent operations +- `Screen Recording` only when using Copilot Vision + +See [TROUBLESHOOTING.md](../TROUBLESHOOTING.md) for exact macOS settings locations. + +## Model configuration + +### GitHub-hosted models + +After signing in, open Chat or Agent Mode and use the model picker to select the available Copilot-backed models for your account. + +### Bring Your Own Key (BYOK) + +Use BYOK when you want to supply provider-specific credentials instead of relying only on your Copilot account. + +Supported providers are documented in [Docs/BYOK.md](../Docs/BYOK.md), including: + +- Anthropic +- Azure +- Gemini +- Groq +- OpenAI +- OpenRouter + +General flow: + +1. Open Chat. +2. Choose `Manage Models` from the model picker. +3. Add the provider credentials required by your model vendor. +4. Enable the saved models you want to expose in Xcode. + +Never commit provider secrets, API keys, or tokens into project files. + +## Workspace-aware features + +For best results with chat, code review, and agent mode: + +- Open the `.xcodeproj`, `.xcworkspace`, or playground before starting a task. +- Allow file-system access if macOS prompts you. +- Keep the relevant files open in Xcode so Copilot can use editor context. + +## Prompt and instruction files + +You can further customize Copilot behavior with: + +- [Prompt files](../Docs/PromptFiles.md) +- [Custom instructions](../Docs/CustomInstructions.md) + +## Error recovery + +If configuration stops working: + +1. Check whether the app is still signed in. +2. Verify the required permissions are still enabled. +3. Restart Xcode after changing extension or accessibility settings. +4. Reopen the Copilot app and retry the action. +5. Collect logs from `~/Library/Logs/GitHubCopilot/` if the issue persists. diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md new file mode 100644 index 00000000..3224dd78 --- /dev/null +++ b/docs/EXAMPLES.md @@ -0,0 +1,43 @@ +# Common Xcode Scenarios + +These examples show practical ways to use GitHub Copilot for Xcode during everyday development. + +## Generate a SwiftUI view + +1. Open a Swift file in Xcode. +2. Add a comment such as `// Build a settings screen with toggles for notifications and analytics`. +3. Pause to let inline completion generate the first draft. +4. Accept the suggestion with `Tab` or open Chat to refine the layout. + +## Explain Objective-C or mixed-language code + +1. Select the code you want to understand. +2. Open Chat from `Editor > GitHub Copilot > Open Chat`. +3. Ask: `Explain what this Objective-C bridge is doing and where Swift calls into it.` + +## Refactor with Agent Mode + +1. Open the relevant workspace. +2. Start Agent Mode from the Copilot UI. +3. Ask for a focused task such as `Rename this service to match the new API contract and update callers.` +4. Review the proposed file edits before accepting them. + +## Add tests for an existing type + +1. Open the source file and any nearby test target. +2. In Chat, ask `Create unit tests for the error handling in this type using the existing test style.` +3. Use the generated test as a starting point, then run the matching test target in Xcode. + +## Review a local change + +1. Make your code changes in Xcode. +2. Open Copilot Code Review. +3. Ask for a review focused on regressions, missing edge cases, or API misuse. +4. Apply any fixes and rerun the relevant tests. + +## Configure BYOK for a provider-specific model + +1. Open Chat and select `Manage Models`. +2. Add the provider details and API key for the model you want. +3. Enable the model and switch to it from the model picker. +4. Keep secrets in the app configuration only, not in source-controlled files. diff --git a/docs/FEATURES.md b/docs/FEATURES.md new file mode 100644 index 00000000..95537383 --- /dev/null +++ b/docs/FEATURES.md @@ -0,0 +1,58 @@ +# Features + +GitHub Copilot for Xcode combines inline assistance, chat, and workspace-aware tooling in one macOS app and Xcode extension. + +## Xcode integration features + +### Inline code completion + +- Multiline code suggestions while you type +- `Tab` to accept the first line +- `Option` to preview the full suggestion +- `Option` + `Tab` to accept the full suggestion + +### Chat + +- Open chat from `Editor > GitHub Copilot > Open Chat` +- Ask for explanations, refactors, tests, and implementation help +- Attach workspace files and directories for more grounded responses + +### Agent Mode + +- Applies edits directly to project files +- Searches the codebase and related files +- Runs terminal commands and shows output without leaving the workflow +- Uses MCP tools when you configure supported servers + +### Code review + +- Reviews local changes from inside the Xcode experience +- Surfaces likely issues before you commit +- Works best when the workspace is open and file permissions are granted + +### Next Edit Suggestions + +- Helps step through follow-up edits after a change is accepted +- Keeps the suggestion flow moving across related files and regions + +### Copilot Vision + +- Adds optional screen-based context capture +- Requires Screen Recording permission only when you choose to use it + +### Bring Your Own Key (BYOK) + +- Supports external model providers alongside standard Copilot sign-in +- Lets you enable only the providers and models your team needs +- Detailed setup lives in [Docs/BYOK.md](../Docs/BYOK.md) + +### Prompt files and custom instructions + +- Prompt files provide reusable context for common tasks +- Custom instructions tune how Copilot responds in chat and agent workflows + +## Compatibility notes + +- The Swift packages in `Core/Package.swift` and `Tool/Package.swift` declare `macOS 12` as the minimum supported platform. +- Several UI integrations add richer behavior on newer macOS releases such as 13, 14, and later through availability checks in the app code. +- The repository archives the app through the `Copilot for Xcode` workspace scheme in `.github/workflows/codeql.yml` and `Script/localbuild-app.sh`. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 00000000..ba48a807 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,50 @@ +# Installation + +Use this guide when you need a clean installation of **GitHub Copilot for Xcode** on a Mac. + +## Prerequisites + +- macOS 12 or later +- A version of Xcode that supports Source Editor Extensions +- A GitHub account with GitHub Copilot access, or BYOK provider credentials if you plan to use external models + +## Install with Homebrew + +```sh +brew install --cask github-copilot-for-xcode +``` + +## Install from a DMG + +1. Download the latest DMG from the [latest release](https://github.com/github/CopilotForXcode/releases/latest/download/GitHubCopilotForXcode.dmg). +2. Drag `GitHub Copilot for Xcode` into `/Applications`. +3. Launch the app once so macOS can register the helper processes and extension. + +## First Launch Checklist + +1. Open `GitHub Copilot for Xcode` from `/Applications`. +2. Accept the macOS security prompt for the downloaded app. +3. Allow the background item used to connect the host app and the Xcode extension. +4. Turn on `Accessibility` access when prompted. +5. Open `Extension Permission` from the app settings and enable `GitHub Copilot` under `Xcode Source Editor`. +6. Start Xcode and verify that `Editor > GitHub Copilot` is available. + +## Sign In + +1. In the app, click `Sign in`. +2. Complete the browser-based device flow with the GitHub account that owns your Copilot entitlement. +3. Return to the app after authorization finishes. + +## Optional: Bring Your Own Key (BYOK) + +If you want to use your own model provider credentials, continue with the [configuration guide](./CONFIGURATION.md) and the existing [BYOK reference](../Docs/BYOK.md). + +## Updating + +- Use `Check for Updates` from the app or status menu. +- Restart Xcode after installing an update. +- If you reinstall from a DMG, relaunch the app once before reopening Xcode. + +## Troubleshooting + +If setup fails, use the repository's [troubleshooting guide](../TROUBLESHOOTING.md) for permission, login item, and extension issues.