diff --git a/CopilotForXcodeExtension/CopilotForXcodeExtension.entitlements b/CopilotForXcodeExtension/CopilotForXcodeExtension.entitlements new file mode 100644 index 0000000..13cb114 --- /dev/null +++ b/CopilotForXcodeExtension/CopilotForXcodeExtension.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/CopilotForXcodeExtension/CopilotForXcodeExtension.swift b/CopilotForXcodeExtension/CopilotForXcodeExtension.swift new file mode 100644 index 0000000..4e07a20 --- /dev/null +++ b/CopilotForXcodeExtension/CopilotForXcodeExtension.swift @@ -0,0 +1,91 @@ +import CopilotForXcodeKit +import Foundation +import OSLog + +struct FileState { + var contentHashValue: Int = 0 + var isDirty: Bool = false +} + +func log(message: String, type: OSLogType = .info) { + os_log( + "%{public}@", + log: .init(subsystem: "com.intii.XccurateFormatter", category: "Auto Formatting"), + type: type, + message as CVarArg + ) +} + +@main +class Extension: CopilotForXcodeExtension { + var host: HostServer? + var suggestionService: SuggestionServiceType? { nil } + var chatService: ChatServiceType? { nil } + var promptToCodeService: PromptToCodeServiceType? { nil } + var sceneConfiguration: SceneConfiguration { .init() } + + required init() {} + + @MainActor + var files: [URL: FileState] = [:] + @MainActor + var unhandledFileURLs: Set = [] + + @MainActor + func workspace(_ workspace: WorkspaceInfo, didUpdateDocumentAt fileURL: URL, content: String) { + updateFile(at: fileURL, content: content) + } + + func workspace(_ workspace: WorkspaceInfo, didSaveDocumentAt fileURL: URL) { + Task { await formatFile(at: fileURL) } + } + + @MainActor + func workspace(_ workspace: WorkspaceInfo, didCloseDocumentAt fileURL: URL) { + files[fileURL] = nil + } +} + +struct SceneConfiguration: CopilotForXcodeExtensionSceneConfiguration { + typealias ChatPanelSceneGroup = Never + typealias SuggestionPanelSceneGroup = Never +} + +extension Extension { + @MainActor + func updateFile(at url: URL, content: String) { + var state = files[url] ?? .init() + state.isDirty = content.hashValue != state.contentHashValue + state.contentHashValue = content.hashValue + files[url] = state + } + + @MainActor + func formatFile(at url: URL) async { + guard var state = files[url], state.isDirty else { + unhandledFileURLs.remove(url) + return + } + // Only trigger format if the file is active. + guard let editor = try? await host?.getActiveEditor() else { return } + guard editor.documentURL == url else { + // when user switch to another editor, we should format the file when user switch back. + unhandledFileURLs.insert(url) + return + } + + do { + try await host?.triggerExtensionCommand( + extensionName: "Xccurate Formatter", + command: "Format File", + activateXcode: false + ) + unhandledFileURLs.remove(url) + state.isDirty = false + files[url] = state + } catch { + log(message: error.localizedDescription, type: .error) + } + } +} + diff --git a/CopilotForXcodeExtension/Info.plist b/CopilotForXcodeExtension/Info.plist new file mode 100644 index 0000000..625f7fa --- /dev/null +++ b/CopilotForXcodeExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + EXAppExtensionAttributes + + EXExtensionPointIdentifier + com.intii.CopilotForXcode.ExtensionService.Extension + + + diff --git a/XccurateFormatter.xcodeproj/project.pbxproj b/XccurateFormatter.xcodeproj/project.pbxproj index d655fd6..6af9dbb 100644 --- a/XccurateFormatter.xcodeproj/project.pbxproj +++ b/XccurateFormatter.xcodeproj/project.pbxproj @@ -19,6 +19,9 @@ C84B0A812936EFB600295D83 /* UTIToExtensionName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A29D9C292F69A200FF3F43 /* UTIToExtensionName.swift */; }; C84B0A842936EFBC00295D83 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A29DAF2930637700FF3F43 /* UserDefaultsKeys.swift */; }; C84B0A852936F13700295D83 /* XccurateFormatterService in CopyFiles */ = {isa = PBXBuildFile; fileRef = C84B0A762936EF2000295D83 /* XccurateFormatterService */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + C86C4B282B4E888F00812C13 /* CopilotForXcodeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86C4B272B4E888F00812C13 /* CopilotForXcodeExtension.swift */; }; + C86C4B2D2B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = C86C4B252B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + C86C4B332B4E89FA00812C13 /* CopilotForXcodeKit in Frameworks */ = {isa = PBXBuildFile; productRef = C86C4B322B4E89FA00812C13 /* CopilotForXcodeKit */; }; C89A95FF2934A1AA00351C15 /* SwiftFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89A95FE2934A1AA00351C15 /* SwiftFormatTests.swift */; }; C89A960B2934A6C700351C15 /* TestService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89A960A2934A6C700351C15 /* TestService.swift */; }; C89A960E2934AF8800351C15 /* TestConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89A960D2934AF8800351C15 /* TestConfig.swift */; }; @@ -54,6 +57,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + C86C4B2B2B4E888F00812C13 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C89F25D5292F203300C83434 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C86C4B242B4E888F00812C13; + remoteInfo = CopilotForXcodeExtension; + }; C885C0CC294AAEE300D5738C /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = C89F25D5292F203300C83434 /* Project object */; @@ -87,6 +97,17 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + C86C4B202B4E886600812C13 /* Embed ExtensionKit Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(EXTENSIONS_FOLDER_PATH)"; + dstSubfolderSpec = 16; + files = ( + C86C4B2D2B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex in Embed ExtensionKit Extensions */, + ); + name = "Embed ExtensionKit Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; C871E3CC2936E64200740832 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 12; @@ -143,6 +164,10 @@ C8253E112936F64B00E95CCA /* LaunchAgentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAgentManager.swift; sourceTree = ""; }; C8253E1429373A8500E95CCA /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C84B0A762936EF2000295D83 /* XccurateFormatterService */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = XccurateFormatterService; sourceTree = BUILT_PRODUCTS_DIR; }; + C86C4B252B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = "XccurateFormatterCopilotForXcodeExtension Debug.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + C86C4B272B4E888F00812C13 /* CopilotForXcodeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotForXcodeExtension.swift; sourceTree = ""; }; + C86C4B292B4E888F00812C13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C86C4B2A2B4E888F00812C13 /* CopilotForXcodeExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CopilotForXcodeExtension.entitlements; sourceTree = ""; }; C888032929435AF6009EB4F4 /* Config.debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.debug.xcconfig; sourceTree = ""; }; C88829A2297155120081869F /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; C89A95FC2934A1AA00351C15 /* EditorExtensionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EditorExtensionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -197,6 +222,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C86C4B222B4E888F00812C13 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C86C4B332B4E89FA00812C13 /* CopilotForXcodeKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C89A95F92934A1AA00351C15 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -223,6 +256,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + C86C4B262B4E888F00812C13 /* CopilotForXcodeExtension */ = { + isa = PBXGroup; + children = ( + C86C4B272B4E888F00812C13 /* CopilotForXcodeExtension.swift */, + C86C4B292B4E888F00812C13 /* Info.plist */, + C86C4B2A2B4E888F00812C13 /* CopilotForXcodeExtension.entitlements */, + ); + path = CopilotForXcodeExtension; + sourceTree = ""; + }; C89A95FD2934A1AA00351C15 /* EditorExtensionTests */ = { isa = PBXGroup; children = ( @@ -249,6 +292,7 @@ C89F25F9292F420900C83434 /* EditorExtension */, C89F260C292F425E00C83434 /* EditorExtensionXPCService */, C89A95FD2934A1AA00351C15 /* EditorExtensionTests */, + C86C4B262B4E888F00812C13 /* CopilotForXcodeExtension */, C89F25F4292F420900C83434 /* Frameworks */, C89F25DE292F203300C83434 /* Products */, ); @@ -261,6 +305,7 @@ C89F25F3292F420900C83434 /* Xccurate Formatter.appex */, C89A95FC2934A1AA00351C15 /* EditorExtensionTests.xctest */, C84B0A762936EF2000295D83 /* XccurateFormatterService */, + C86C4B252B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex */, ); name = Products; sourceTree = ""; @@ -352,6 +397,26 @@ productReference = C84B0A762936EF2000295D83 /* XccurateFormatterService */; productType = "com.apple.product-type.tool"; }; + C86C4B242B4E888F00812C13 /* CopilotForXcodeExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = C86C4B2E2B4E888F00812C13 /* Build configuration list for PBXNativeTarget "CopilotForXcodeExtension" */; + buildPhases = ( + C86C4B212B4E888F00812C13 /* Sources */, + C86C4B222B4E888F00812C13 /* Frameworks */, + C86C4B232B4E888F00812C13 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CopilotForXcodeExtension; + packageProductDependencies = ( + C86C4B322B4E89FA00812C13 /* CopilotForXcodeKit */, + ); + productName = CopilotForXcodeExtension; + productReference = C86C4B252B4E888F00812C13 /* XccurateFormatterCopilotForXcodeExtension Debug.appex */; + productType = "com.apple.product-type.extensionkit-extension"; + }; C89A95FB2934A1AA00351C15 /* EditorExtensionTests */ = { isa = PBXNativeTarget; buildConfigurationList = C89A96022934A1AA00351C15 /* Build configuration list for PBXNativeTarget "EditorExtensionTests" */; @@ -380,12 +445,14 @@ C871E3CC2936E64200740832 /* CopyFiles */, C89F25DB292F203300C83434 /* Resources */, C89F2606292F420900C83434 /* Embed Foundation Extensions */, + C86C4B202B4E886600812C13 /* Embed ExtensionKit Extensions */, ); buildRules = ( ); dependencies = ( C885C0CD294AAEE300D5738C /* PBXTargetDependency */, C89F2601292F420900C83434 /* PBXTargetDependency */, + C86C4B2C2B4E888F00812C13 /* PBXTargetDependency */, ); name = XccurateFormatter; productName = XccurateFormatter; @@ -418,12 +485,15 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1410; + LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1410; TargetAttributes = { C84B0A752936EF2000295D83 = { CreatedOnToolsVersion = 14.1; }; + C86C4B242B4E888F00812C13 = { + CreatedOnToolsVersion = 15.0.1; + }; C89A95FB2934A1AA00351C15 = { CreatedOnToolsVersion = 14.1; }; @@ -444,6 +514,9 @@ Base, ); mainGroup = C89F25D4292F203300C83434; + packageReferences = ( + C86C4B312B4E89FA00812C13 /* XCRemoteSwiftPackageReference "CopilotForXcodeKit" */, + ); productRefGroup = C89F25DE292F203300C83434 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -452,11 +525,19 @@ C89F25F2292F420900C83434 /* EditorExtension */, C89A95FB2934A1AA00351C15 /* EditorExtensionTests */, C84B0A752936EF2000295D83 /* EditorExtensionXPCCLI */, + C86C4B242B4E888F00812C13 /* CopilotForXcodeExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + C86C4B232B4E888F00812C13 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C89A95FA2934A1AA00351C15 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -499,6 +580,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C86C4B212B4E888F00812C13 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C86C4B282B4E888F00812C13 /* CopilotForXcodeExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C89A95F82934A1AA00351C15 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -551,6 +640,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + C86C4B2C2B4E888F00812C13 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C86C4B242B4E888F00812C13 /* CopilotForXcodeExtension */; + targetProxy = C86C4B2B2B4E888F00812C13 /* PBXContainerItemProxy */; + }; C885C0CD294AAEE300D5738C /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = C84B0A752936EF2000295D83 /* EditorExtensionXPCCLI */; @@ -603,6 +697,69 @@ }; name = Release; }; + C86C4B2F2B4E888F00812C13 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CODE_SIGN_ENTITLEMENTS = CopilotForXcodeExtension/CopilotForXcodeExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5YKZ4Y3DAW; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CopilotForXcodeExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Auto Formatting"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).CopilotForXcodeExtension"; + PRODUCT_NAME = "XccurateFormatter$(TARGET_NAME) Debug"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + C86C4B302B4E888F00812C13 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CODE_SIGN_ENTITLEMENTS = CopilotForXcodeExtension/CopilotForXcodeExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5YKZ4Y3DAW; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = CopilotForXcodeExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Auto Formatting"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_IDENTIFIER_BASE).CopilotForXcodeExtension"; + PRODUCT_NAME = "XccurateFormatter$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; C89A96002934A1AA00351C15 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -884,6 +1041,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C86C4B2E2B4E888F00812C13 /* Build configuration list for PBXNativeTarget "CopilotForXcodeExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C86C4B2F2B4E888F00812C13 /* Debug */, + C86C4B302B4E888F00812C13 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C89A96022934A1AA00351C15 /* Build configuration list for PBXNativeTarget "EditorExtensionTests" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -921,6 +1087,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + C86C4B312B4E89FA00812C13 /* XCRemoteSwiftPackageReference "CopilotForXcodeKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/intitni/CopilotForXcodeKit.git"; + requirement = { + branch = "feature/passing-file-content-to-extension"; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + C86C4B322B4E89FA00812C13 /* CopilotForXcodeKit */ = { + isa = XCSwiftPackageProductDependency; + package = C86C4B312B4E89FA00812C13 /* XCRemoteSwiftPackageReference "CopilotForXcodeKit" */; + productName = CopilotForXcodeKit; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = C89F25D5292F203300C83434 /* Project object */; } diff --git a/XccurateFormatter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XccurateFormatter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..c21338d --- /dev/null +++ b/XccurateFormatter.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "codablewrappers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/GottaGetSwifty/CodableWrappers.git", + "state" : { + "revision" : "4eb46a4c656333e8514db8aad204445741de7d40", + "version" : "2.0.7" + } + }, + { + "identity" : "copilotforxcodekit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/intitni/CopilotForXcodeKit.git", + "state" : { + "branch" : "feature/passing-file-content-to-extension", + "revision" : "bdc3693bc12e1aa88ddbd2549383d92ce6312058" + } + } + ], + "version" : 2 +} diff --git a/XccurateFormatter/XccurateFormatterApp.swift b/XccurateFormatter/XccurateFormatterApp.swift index 253e213..945b9fd 100644 --- a/XccurateFormatter/XccurateFormatterApp.swift +++ b/XccurateFormatter/XccurateFormatterApp.swift @@ -8,3 +8,4 @@ struct XccurateFormatterApp: App { } } } +