Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions Copilot for Xcode/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ import UpdateChecker
import XPCShared

struct VisualEffect: NSViewRepresentable {
func makeNSView(context: Self.Context) -> NSView { return NSVisualEffectView() }
func updateNSView(_ nsView: NSView, context: Context) { }
func makeNSView(context: Self.Context) -> NSView { return NSVisualEffectView() }
func updateNSView(_ nsView: NSView, context: Context) {}
}

class TheUpdateCheckerDelegate: UpdateCheckerDelegate {
func prepareForRelaunch(finish: @escaping () -> Void) {
Task {
let service = try? getService()
try? await service?.quitService()
finish()
}
}
}

let updateCheckerDelegate = TheUpdateCheckerDelegate()

@main
struct CopilotForXcodeApp: App {
var body: some Scene {
Expand All @@ -20,7 +32,17 @@ struct CopilotForXcodeApp: App {
.onAppear {
UserDefaults.setupDefaultSettings()
}
.environment(\.updateChecker, UpdateChecker(hostBundle: Bundle.main))
.environment(
\.updateChecker,
{
let checker = UpdateChecker(
hostBundle: Bundle.main,
shouldAutomaticallyCheckForUpdate: false
)
checker.updateCheckerDelegate = updateCheckerDelegate
return checker
}()
)
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions Core/Sources/HostApp/DebugView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class DebugSettings: ObservableObject {

struct DebugSettingsView: View {
@StateObject var settings = DebugSettings()
@Environment(\.updateChecker) var updateChecker

var body: some View {
ScrollView {
Expand Down Expand Up @@ -134,6 +135,10 @@ struct DebugSettingsView: View {
) {
Text("Use Cloudflare domain name for license check")
}

Button("Reset update cycle") {
updateChecker.resetUpdateCycle()
}
}
}
.frame(maxWidth: .infinity)
Expand Down
5 changes: 4 additions & 1 deletion Core/Sources/HostApp/TabContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ private extension EnvironmentValues {
}

struct UpdateCheckerKey: EnvironmentKey {
static var defaultValue: UpdateChecker = .init(hostBundle: nil)
static var defaultValue: UpdateChecker = .init(
hostBundle: nil,
shouldAutomaticallyCheckForUpdate: false
)
}

public extension EnvironmentValues {
Expand Down
2 changes: 2 additions & 0 deletions Core/Sources/Service/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Combine
import Dependencies
import Foundation
import GitHubCopilotService
import Logger
import SuggestionService
import Toast
import Workspace
Expand Down Expand Up @@ -103,6 +104,7 @@ public final class Service {

@MainActor
public func prepareForExit() async {
Logger.service.info("Prepare for exit.")
#if canImport(ProService)
proService.prepareForExit()
#endif
Expand Down
7 changes: 7 additions & 0 deletions Core/Sources/Service/XPCService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ public class XPCService: NSObject, XPCServiceProtocol {
reply()
NotificationCenter.default.post(name: .init(name), object: nil)
}

public func quit(reply: @escaping () -> Void) {
Task {
await Service.shared.prepareForExit()
reply()
}
}

// MARK: - Requests

Expand Down
56 changes: 54 additions & 2 deletions Core/Sources/UpdateChecker/UpdateChecker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ import Sparkle
public final class UpdateChecker {
let updater: SPUUpdater
let hostBundleFound: Bool
let delegate = UpdaterDelegate()
let delegate: UpdaterDelegate
public weak var updateCheckerDelegate: UpdateCheckerDelegate? {
get { delegate.updateCheckerDelegate }
set { delegate.updateCheckerDelegate = newValue }
}

public init(hostBundle: Bundle?) {
public init(
hostBundle: Bundle?,
shouldAutomaticallyCheckForUpdate: Bool
) {
if hostBundle == nil {
hostBundleFound = false
Logger.updateChecker.error("Host bundle not found")
} else {
hostBundleFound = true
}
delegate = .init(
shouldAutomaticallyCheckForUpdate: shouldAutomaticallyCheckForUpdate
)
updater = SPUUpdater(
hostBundle: hostBundle ?? Bundle.main,
applicationBundle: Bundle.main,
Expand All @@ -30,14 +40,56 @@ public final class UpdateChecker {
public func checkForUpdates() {
updater.checkForUpdates()
}

public func resetUpdateCycle() {
updater.resetUpdateCycleAfterShortDelay()
}

public var automaticallyChecksForUpdates: Bool {
get { updater.automaticallyChecksForUpdates }
set { updater.automaticallyChecksForUpdates = newValue }
}
}

public protocol UpdateCheckerDelegate: AnyObject {
func prepareForRelaunch(finish: @escaping () -> Void)
}

class UpdaterDelegate: NSObject, SPUUpdaterDelegate {
let shouldAutomaticallyCheckForUpdate: Bool
weak var updateCheckerDelegate: UpdateCheckerDelegate?

init(shouldAutomaticallyCheckForUpdate: Bool) {
self.shouldAutomaticallyCheckForUpdate = shouldAutomaticallyCheckForUpdate
}

func updater(_ updater: SPUUpdater, mayPerform updateCheck: SPUUpdateCheck) throws {
// Not sure how it works
// if !shouldAutomaticallyCheckForUpdate, updateCheck == .updatesInBackground {
// throw CancellationError()
// }
}

func updater(
_ updater: SPUUpdater,
shouldPostponeRelaunchForUpdate item: SUAppcastItem,
untilInvokingBlock installHandler: @escaping () -> Void
) -> Bool {
if let updateCheckerDelegate {
updateCheckerDelegate.prepareForRelaunch(finish: installHandler)
return true
}
return false
}

func updater(_ updater: SPUUpdater, willScheduleUpdateCheckAfterDelay delay: TimeInterval) {
Logger.updateChecker.info("Will schedule update check after delay: \(delay)")
}

func updaterWillNotScheduleUpdateCheck(_ updater: SPUUpdater) {
Logger.updateChecker.info("Will not schedule update check")
}

func allowedChannels(for updater: SPUUpdater) -> Set<String> {
if UserDefaults.shared.value(for: \.installBetaBuilds) {
Set(["beta"])
Expand Down
20 changes: 15 additions & 5 deletions ExtensionService/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
let service = Service.shared
var statusBarItem: NSStatusItem!
var xpcController: XPCController?
let updateChecker =
UpdateChecker(
hostBundle: locateHostBundleURL(url: Bundle.main.bundleURL)
.flatMap(Bundle.init(url:))
)
let updateChecker = UpdateChecker(
hostBundle: locateHostBundleURL(url: Bundle.main.bundleURL)
.flatMap(Bundle.init(url:)),
shouldAutomaticallyCheckForUpdate: true
)

func applicationDidFinishLaunching(_: Notification) {
if ProcessInfo.processInfo.environment["IS_UNIT_TEST"] == "YES" { return }
_ = XcodeInspector.shared
updateChecker.updateCheckerDelegate = self
service.start()
AXIsProcessTrustedWithOptions([
kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true,
Expand Down Expand Up @@ -138,6 +139,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
}
}

extension AppDelegate: UpdateCheckerDelegate {
func prepareForRelaunch(finish: @escaping () -> Void) {
Task {
await service.prepareForExit()
finish()
}
}
}

extension NSRunningApplication {
var isUserOfService: Bool {
[
Expand Down
2 changes: 1 addition & 1 deletion Pro
Submodule Pro updated from b915eb to d58fa1
7 changes: 6 additions & 1 deletion Tool/Sources/CodeiumService/CodeiumExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import Logger
import Preferences
import Workspace

@globalActor public enum CodeiumActor {
public actor TheActor {}
public static let shared = TheActor()
}

public final class CodeiumExtension: BuiltinExtension {
public var suggestionServiceId: Preferences.BuiltInSuggestionFeatureProvider { .codeium }

Expand Down Expand Up @@ -119,7 +124,7 @@ final class ServiceLocator {
guard let workspace = workspacePool.workspaces[workspace.workspaceURL],
let plugin = workspace.plugin(for: CodeiumWorkspacePlugin.self)
else { return nil }
return plugin.codeiumService
return await plugin.codeiumService
}
}

18 changes: 13 additions & 5 deletions Tool/Sources/CodeiumService/CodeiumService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class CodeiumService {
var cancellationCounter: UInt64 = 0
let openedDocumentPool = OpenedDocumentPool()
let onServiceLaunched: () -> Void
let onServiceTerminated: () -> Void

let languageServerURL: URL
let supportURL: URL
Expand All @@ -63,13 +64,19 @@ public class CodeiumService {
projectRootURL = URL(fileURLWithPath: "/")
server = designatedServer
onServiceLaunched = {}
onServiceTerminated = {}
languageServerURL = URL(fileURLWithPath: "/")
supportURL = URL(fileURLWithPath: "/")
}

public init(projectRootURL: URL, onServiceLaunched: @escaping () -> Void) throws {
public init(
projectRootURL: URL,
onServiceLaunched: @escaping () -> Void,
onServiceTerminated: @escaping () -> Void
) throws {
self.projectRootURL = projectRootURL
self.onServiceLaunched = onServiceLaunched
self.onServiceTerminated = onServiceTerminated
let urls = try CodeiumService.createFoldersIfNeeded()
languageServerURL = urls.executableURL.appendingPathComponent("language_server")
supportURL = urls.supportURL
Expand Down Expand Up @@ -117,6 +124,7 @@ public class CodeiumService {
self?.heartbeatTask?.cancel()
self?.requestCounter = 0
self?.cancellationCounter = 0
self?.onServiceTerminated()
Logger.codeium.info("Language server is terminated, will be restarted when needed.")
}

Expand Down Expand Up @@ -186,7 +194,7 @@ extension CodeiumService {
throw E()
}
var ideVersion = await XcodeInspector.shared.safe.latestActiveXcode?.version
?? fallbackXcodeVersion
?? fallbackXcodeVersion
let versionNumberSegmentCount = ideVersion.split(separator: ".").count
if versionNumberSegmentCount == 2 {
ideVersion += ".0"
Expand Down Expand Up @@ -237,8 +245,8 @@ extension CodeiumService: CodeiumSuggestionServiceType {
let relativePath = getRelativePath(of: fileURL)

let task = Task {
let request = await CodeiumRequest.GetCompletion(requestBody: .init(
metadata: try getMetadata(),
let request = try await CodeiumRequest.GetCompletion(requestBody: .init(
metadata: getMetadata(),
document: .init(
absolute_path: fileURL.path,
relative_path: relativePath,
Expand Down Expand Up @@ -266,7 +274,7 @@ extension CodeiumService: CodeiumSuggestionServiceType {

try Task.checkCancellation()

let result = try await (try await setupServerIfNeeded()).sendRequest(request)
let result = try await (await setupServerIfNeeded()).sendRequest(request)

try Task.checkCancellation()

Expand Down
12 changes: 9 additions & 3 deletions Tool/Sources/CodeiumService/CodeiumWorkspacePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Logger
import Workspace

public final class CodeiumWorkspacePlugin: WorkspacePlugin {
var _codeiumService: CodeiumService?
private var _codeiumService: CodeiumService?
@CodeiumActor
var codeiumService: CodeiumService? {
if let service = _codeiumService { return service }
do {
Expand All @@ -15,23 +16,28 @@ public final class CodeiumWorkspacePlugin: WorkspacePlugin {
}

deinit {
if let codeiumService {
codeiumService.terminate()
if let _codeiumService {
_codeiumService.terminate()
}
}

@CodeiumActor
func createCodeiumService() throws -> CodeiumService {
let newService = try CodeiumService(
projectRootURL: projectRootURL,
onServiceLaunched: {
[weak self] in
self?.finishLaunchingService()
},
onServiceTerminated: {
// start handled in the service.
}
)
_codeiumService = newService
return newService
}

@CodeiumActor
func finishLaunchingService() {
guard let workspace, let _codeiumService else { return }
Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ final class ServiceLocator {
guard let workspace = workspacePool.workspaces[workspace.workspaceURL],
let plugin = workspace.plugin(for: GitHubCopilotWorkspacePlugin.self)
else { return nil }
return plugin.gitHubCopilotService
return await plugin.gitHubCopilotService
}
}

Loading