Skip to content

intitni/CopilotForXcodeKit

Repository files navigation

CopilotForXcodeKit

CopilotforXcodeKit is a Swift package that allow you to build extensions for Copilot for Xcode.

Roadmap

  • In-app Configuration Screen
  • Suggestion Service
  • Custom Chat Tab
  • Chat Service
  • Prompt to Code Service
  • APIs for other Xcode Source Editor Extensions
  • Custom Models

Building an Extension

1. Create an Extension Target

Create a Generic Extension target. The extension should target macOS 13+. It can be sandboxed.

In the info.plist of the target, set ExtensionPointIdentifier to com.intii.CopilotForXcode.ExtensionService.Extension:

<key>EXAppExtensionAttributes</key>
<dict>
    <key>EXExtensionPointIdentifier</key>
    <string>com.intii.CopilotForXcode.ExtensionService.Extension</string>
</dict>

2. Add CopilotForXcodeKit as a Dependency

Add this repo as an dependency in Package.swift or via UI in Xcode.

3. Implement the Extension

Create a class that conforms to CopilotForXcodeExtension, and mark it with @main.

import CopilotForXcodeKit

@main
class Extension: CopilotForXcodeExtension {
    ...
}

3.1 Communicate via Global Connection

When the extension is turned on in Copilot for Xcode, the connection will be established. connectionDidActivate(connectedTo:) will be called and host will be set.

Once host is set, you can use it to communicate with Copilot for Xcode.

class Extension: CopilotForXcodeExtension {
    var host: HostServer?
    
    func connectionDidActivate(connectedTo host: HostServer) {
        Task {
            try await host.toast("Connected to Example Extension")
        }
    }
    
    ...
}

Copilot for Xcode will occasionally communicate with your extension to provide updates about Xcode. You can implement observer methods such as workspace(_:didOpenFileAt:) to receive this information.

3.2 Provide Configuration Screen

To provide a configuration screen that users can access from the extension list, you must

  • Create a type that conforms to CopilotForXcodeExtensionSceneConfiguration. Then, return a scene through the configurationScene property.
  • Return the configuration from the sceneConfiguration property of the extension.
struct SceneConfiguration: CopilotForXcodeExtensionSceneConfiguration {
    var configurationScene: ConfigurationScene<ConfigurationView>? {
        .init { viewModel in
            ConfigurationView(model: viewModel)
        } onConnection: { _ in
            return true
        }
    }
}

The ConfigurationView is instantiated when the configuration screen is opened, and the connection is established at this point.

You can then utilize the host property of the view model to communicate with Copilot for Xcode.

struct ConfigurationView: View {
    @ObservedObject var model: CopilotForXcodeSceneModel
    var body: some View {
        Button("Toast") {
            Task { @MainActor in
                try? await model.host?.toast("Hello")
            }
        }
    }
}

3.3 Provide Suggestion Service

To enable the suggestion service within your extension, you must:

  • Implement a type that conforms to the SuggestionServiceType protocol.
  • Provide an instance of this type by returning it from the suggestionService property.
class SuggestionService: SuggestionServiceType {
    var configuration: SuggestionServiceConfiguration {
        .init(acceptsRelevantCodeSnippets: true)
    }

    func getSuggestions(
        _ request: SuggestionRequest,
        workspace: WorkspaceInfo
    ) async throws -> [CodeSuggestion] {
        [
            .init(
                id: UUID().uuidString,
                text: "Hello World",
                position: request.cursorPosition,
                range: .init(start: request.cursorPosition, end: request.cursorPosition)
            ),
        ]
    }
    
    ...
}

If you need to maintain multiple suggestion services for different workspaces, such as those utilizing language servers, you will be responsible for managing the lifecycle of each service individually.

4. Debugging the Extension

To debug the extension, you have two options: running it directly in Xcode or simply building it. There is no practical difference between the two methods, as Xcode does not automatically attach to the extension's process (may be a bug).

Either way, once the extension is built, it will be available in Copilot for Xcode. Then you need to enable the extension:

  • Click "Extensions" in Copilot for Xcode.app.
  • Click "Select Extensions" to see all available extensions.
  • Enable the extension you want to debug.

After that, the extension process will start to run. You can attach to it from Xcode's Debug menu.

It is recommended to give the debug build a different bundle identifier to prevent conflicts with the release version.

When running the extension from Xcode, you will be prompted to choose a target application. Please select "Copilot for Xcode.app".