Skip to content

Commit 9788b5c

Browse files
committed
Pre-release 0.37.127
1 parent 81fc588 commit 9788b5c

25 files changed

+442
-293
lines changed

Core/Sources/HostApp/General.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ public struct General {
1212
@ObservableState
1313
public struct State: Equatable {
1414
var xpcServiceVersion: String?
15+
var xpcCLSVersion: String?
1516
var isAccessibilityPermissionGranted: ObservedAXStatus = .unknown
1617
var isExtensionPermissionGranted: ExtensionPermissionStatus = .unknown
18+
var xpcServiceAuthStatus: AuthStatus = .init(status: .unknown)
1719
var isReloading = false
1820
}
1921

@@ -24,8 +26,10 @@ public struct General {
2426
case reloadStatus
2527
case finishReloading(
2628
xpcServiceVersion: String,
29+
xpcCLSVersion: String?,
2730
axStatus: ObservedAXStatus,
28-
extensionStatus: ExtensionPermissionStatus
31+
extensionStatus: ExtensionPermissionStatus,
32+
authStatus: AuthStatus
2933
)
3034
case failedReloading
3135
case retryReloading
@@ -90,10 +94,14 @@ public struct General {
9094
let isAccessibilityPermissionGranted = try await service
9195
.getXPCServiceAccessibilityPermission()
9296
let isExtensionPermissionGranted = try await service.getXPCServiceExtensionPermission()
97+
let xpcServiceAuthStatus = try await service.getXPCServiceAuthStatus() ?? .init(status: .unknown)
98+
let xpcCLSVersion = try await service.getXPCCLSVersion()
9399
await send(.finishReloading(
94100
xpcServiceVersion: xpcServiceVersion,
101+
xpcCLSVersion: xpcCLSVersion,
95102
axStatus: isAccessibilityPermissionGranted,
96-
extensionStatus: isExtensionPermissionGranted
103+
extensionStatus: isExtensionPermissionGranted,
104+
authStatus: xpcServiceAuthStatus
97105
))
98106
} else {
99107
toast("Launching service app.", .info)
@@ -114,10 +122,12 @@ public struct General {
114122
}
115123
}.cancellable(id: ReloadStatusCancellableId(), cancelInFlight: true)
116124

117-
case let .finishReloading(version, axStatus, extensionStatus):
125+
case let .finishReloading(version, clsVersion, axStatus, extensionStatus, authStatus):
118126
state.xpcServiceVersion = version
119127
state.isAccessibilityPermissionGranted = axStatus
120128
state.isExtensionPermissionGranted = extensionStatus
129+
state.xpcServiceAuthStatus = authStatus
130+
state.xpcCLSVersion = clsVersion
121131
state.isReloading = false
122132
return .none
123133

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import ComposableArchitecture
22
import GitHubCopilotService
3-
import GitHubCopilotViewModel
43
import SwiftUI
54

65
struct AppInfoView: View {
@@ -15,61 +14,61 @@ struct AppInfoView: View {
1514
@Environment(\.toast) var toast
1615

1716
@StateObject var settings = Settings()
18-
@StateObject var viewModel: GitHubCopilotViewModel
1917

2018
@State var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
2119
@State var automaticallyCheckForUpdates: Bool?
2220

2321
let store: StoreOf<General>
2422

2523
var body: some View {
26-
HStack(alignment: .center, spacing: 16) {
27-
let appImage = if let nsImage = NSImage(named: "AppIcon") {
28-
Image(nsImage: nsImage)
29-
} else {
30-
Image(systemName: "app")
31-
}
32-
appImage
33-
.resizable()
34-
.frame(width: 110, height: 110)
35-
VStack(alignment: .leading, spacing: 8) {
36-
HStack {
37-
Text(Bundle.main.object(forInfoDictionaryKey: "HOST_APP_NAME") as? String ?? "GitHub Copilot for Xcode")
38-
.font(.title)
39-
Text("(\(appVersion ?? ""))")
40-
.font(.title)
24+
WithPerceptionTracking {
25+
HStack(alignment: .center, spacing: 16) {
26+
let appImage = if let nsImage = NSImage(named: "AppIcon") {
27+
Image(nsImage: nsImage)
28+
} else {
29+
Image(systemName: "app")
4130
}
42-
Text("Language Server Version: \(viewModel.version ?? "Loading...")")
43-
Button(action: {
44-
updateChecker.checkForUpdates()
45-
}) {
46-
HStack(spacing: 2) {
47-
Text("Check for Updates")
31+
appImage
32+
.resizable()
33+
.frame(width: 110, height: 110)
34+
VStack(alignment: .leading, spacing: 8) {
35+
HStack {
36+
Text(Bundle.main.object(forInfoDictionaryKey: "HOST_APP_NAME") as? String ?? "GitHub Copilot for Xcode")
37+
.font(.title)
38+
Text("(\(appVersion ?? ""))")
39+
.font(.title)
4840
}
49-
}
50-
HStack {
51-
Toggle(isOn: .init(
52-
get: { automaticallyCheckForUpdates ?? updateChecker.getAutomaticallyChecksForUpdates() },
53-
set: { updateChecker.setAutomaticallyChecksForUpdates($0); automaticallyCheckForUpdates = $0 }
54-
)) {
55-
Text("Automatically Check for Updates")
41+
Text("Language Server Version: \(store.xpcCLSVersion ?? "Loading...")")
42+
Button(action: {
43+
updateChecker.checkForUpdates()
44+
}) {
45+
HStack(spacing: 2) {
46+
Text("Check for Updates")
47+
}
5648
}
57-
58-
Toggle(isOn: $settings.installPrereleases) {
59-
Text("Install pre-releases")
49+
HStack {
50+
Toggle(isOn: .init(
51+
get: { automaticallyCheckForUpdates ?? updateChecker.getAutomaticallyChecksForUpdates() },
52+
set: { updateChecker.setAutomaticallyChecksForUpdates($0); automaticallyCheckForUpdates = $0 }
53+
)) {
54+
Text("Automatically Check for Updates")
55+
}
56+
57+
Toggle(isOn: $settings.installPrereleases) {
58+
Text("Install pre-releases")
59+
}
6060
}
6161
}
62+
Spacer()
6263
}
63-
Spacer()
64+
.padding(.horizontal, 2)
65+
.padding(.vertical, 15)
6466
}
65-
.padding(.horizontal, 2)
66-
.padding(.vertical, 15)
6767
}
6868
}
6969

7070
#Preview {
7171
AppInfoView(
72-
viewModel: GitHubCopilotViewModel.shared,
7372
store: .init(initialState: .init(), reducer: { General() })
7473
)
7574
}

Core/Sources/HostApp/GeneralSettings/CopilotConnectionView.swift

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ComposableArchitecture
22
import GitHubCopilotViewModel
33
import SwiftUI
4+
import Client
45

56
struct CopilotConnectionView: View {
67
@AppStorage("username") var username: String = ""
@@ -18,23 +19,36 @@ struct CopilotConnectionView: View {
1819
}
1920
}
2021
}
22+
23+
var accountStatusString: String {
24+
switch store.xpcServiceAuthStatus.status {
25+
case .loggedIn:
26+
return "Active"
27+
case .notLoggedIn:
28+
return "Not Signed In"
29+
case .notAuthorized:
30+
return "No Subscription"
31+
case .unknown:
32+
return "Loading..."
33+
}
34+
}
2135

2236
var accountStatus: some View {
2337
SettingsButtonRow(
2438
title: "GitHub Account Status Permissions",
25-
subtitle: "GitHub Account: \(viewModel.status?.description ?? "Loading...")"
39+
subtitle: "GitHub Account: \(accountStatusString)"
2640
) {
2741
if viewModel.isRunningAction || viewModel.waitingForSignIn {
2842
ProgressView().controlSize(.small)
2943
}
3044
Button("Refresh Connection") {
31-
viewModel.checkStatus()
45+
store.send(.reloadStatus)
3246
}
3347
if viewModel.waitingForSignIn {
3448
Button("Cancel") {
3549
viewModel.cancelWaiting()
3650
}
37-
} else if viewModel.status == .notSignedIn {
51+
} else if store.xpcServiceAuthStatus.status == .notLoggedIn {
3852
Button("Log in to GitHub") {
3953
viewModel.signIn()
4054
}
@@ -54,21 +68,31 @@ struct CopilotConnectionView: View {
5468
""")
5569
}
5670
}
57-
if viewModel.status == .ok || viewModel.status == .alreadySignedIn ||
58-
viewModel.status == .notAuthorized
59-
{
60-
Button("Log Out from GitHub") { viewModel.signOut()
61-
viewModel.isSignInAlertPresented = false
71+
if store.xpcServiceAuthStatus.status == .loggedIn || store.xpcServiceAuthStatus.status == .notAuthorized {
72+
Button("Log Out from GitHub") {
73+
Task {
74+
viewModel.signOut()
75+
viewModel.isSignInAlertPresented = false
76+
let service = try getService()
77+
do {
78+
try await service.signOutAllGitHubCopilotService()
79+
} catch {
80+
toast(error.localizedDescription, .error)
81+
}
82+
}
6283
}
6384
}
6485
}
6586
}
6687

6788
var connection: some View {
68-
SettingsSection(title: "Account Settings", showWarning: viewModel.status == .notAuthorized) {
89+
SettingsSection(
90+
title: "Account Settings",
91+
showWarning: store.xpcServiceAuthStatus.status == .notAuthorized
92+
) {
6993
accountStatus
7094
Divider()
71-
if viewModel.status == .notAuthorized {
95+
if store.xpcServiceAuthStatus.status == .notAuthorized {
7296
SettingsLink(
7397
url: "https://github.com/features/copilot/plans",
7498
title: "Enable powerful AI features for free with the GitHub Copilot Free plan"
@@ -81,7 +105,7 @@ struct CopilotConnectionView: View {
81105
)
82106
}
83107
.onReceive(DistributedNotificationCenter.default().publisher(for: .authStatusDidChange)) { _ in
84-
viewModel.checkStatus()
108+
store.send(.reloadStatus)
85109
}
86110
}
87111

Core/Sources/HostApp/GeneralView.swift

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,25 @@ struct GeneralView: View {
77
@StateObject private var viewModel = GitHubCopilotViewModel.shared
88

99
var body: some View {
10-
ScrollView {
11-
VStack(alignment: .leading, spacing: 0) {
12-
generalView.padding(20)
13-
Divider()
14-
rightsView.padding(20)
10+
WithPerceptionTracking {
11+
ScrollView {
12+
VStack(alignment: .leading, spacing: 0) {
13+
generalView.padding(20)
14+
Divider()
15+
rightsView.padding(20)
16+
}
17+
.frame(maxWidth: .infinity)
18+
}
19+
.task {
20+
if isPreview { return }
21+
await store.send(.appear).finish()
1522
}
16-
.frame(maxWidth: .infinity)
17-
}
18-
.task {
19-
if isPreview { return }
20-
viewModel.checkStatus()
21-
await store.send(.appear).finish()
2223
}
2324
}
2425

2526
private var generalView: some View {
2627
VStack(alignment: .leading, spacing: 30) {
27-
AppInfoView(viewModel: viewModel, store: store)
28+
AppInfoView(store: store)
2829
GeneralSettingsView(store: store)
2930
CopilotConnectionView(viewModel: viewModel, store: store)
3031
}

Core/Sources/HostApp/MCPConfigView.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,6 @@ struct MCPConfigView: View {
161161
UserDefaults.shared.set(jsonString, for: \.gitHubCopilotMCPConfig)
162162
}
163163

164-
NotificationCenter.default.post(
165-
name: .gitHubCopilotShouldRefreshEditorInformation,
166-
object: nil
167-
)
168-
169164
Task {
170165
let service = try getService()
171166
do {

Core/Sources/HostApp/MCPSettings/CopilotMCPToolManagerObservable.swift

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import SwiftUI
22
import Combine
33
import Persist
44
import GitHubCopilotService
5+
import Client
6+
import Logger
57

68
class CopilotMCPToolManagerObservable: ObservableObject {
79
static let shared = CopilotMCPToolManagerObservable()
@@ -10,23 +12,42 @@ class CopilotMCPToolManagerObservable: ObservableObject {
1012
private var cancellables = Set<AnyCancellable>()
1113

1214
private init() {
13-
// Initial load
14-
availableMCPServerTools = CopilotMCPToolManager.getAvailableMCPServerToolsCollections()
15-
16-
// Setup notification to update when MCP server tools collections change
17-
NotificationCenter.default
15+
DistributedNotificationCenter.default()
1816
.publisher(for: .gitHubCopilotMCPToolsDidChange)
1917
.receive(on: DispatchQueue.main)
2018
.sink { [weak self] _ in
2119
guard let self = self else { return }
22-
self.refreshTools()
20+
Task {
21+
await self.refreshMCPServerTools()
22+
}
2323
}
2424
.store(in: &cancellables)
25+
26+
Task {
27+
// Initial load of MCP server tools collections from ExtensionService process
28+
await refreshMCPServerTools()
29+
}
30+
}
31+
32+
@MainActor
33+
private func refreshMCPServerTools() async {
34+
do {
35+
let service = try getService()
36+
let mcpTools = try await service.getAvailableMCPServerToolsCollections()
37+
refreshTools(tools: mcpTools)
38+
} catch {
39+
Logger.client.error("Failed to fetch MCP server tools: \(error)")
40+
}
2541
}
26-
27-
private func refreshTools() {
28-
self.availableMCPServerTools = CopilotMCPToolManager.getAvailableMCPServerToolsCollections()
29-
AppState.shared.cleanupMCPToolsStatus(availableTools: self.availableMCPServerTools)
30-
AppState.shared.createMCPToolsStatus(self.availableMCPServerTools)
42+
43+
private func refreshTools(tools: [MCPServerToolsCollection]?) {
44+
guard let tools = tools else {
45+
// nil means the tools data is ready, and skip it first.
46+
return
47+
}
48+
49+
AppState.shared.cleanupMCPToolsStatus(availableTools: tools)
50+
AppState.shared.createMCPToolsStatus(tools)
51+
self.availableMCPServerTools = tools
3152
}
3253
}

0 commit comments

Comments
 (0)