diff --git a/Core/Package.swift b/Core/Package.swift index 944fb029..a87d2b6c 100644 --- a/Core/Package.swift +++ b/Core/Package.swift @@ -119,6 +119,7 @@ let package = Package( "LaunchAgentManager", "PlusFeatureFlag", .product(name: "Toast", package: "Tool"), + .product(name: "SharedUIComponents", package: "Tool"), .product(name: "SuggestionModel", package: "Tool"), .product(name: "MarkdownUI", package: "swift-markdown-ui"), .product(name: "OpenAIService", package: "Tool"), diff --git a/Core/Sources/HostApp/AccountSettings/CopilotView.swift b/Core/Sources/HostApp/AccountSettings/CopilotView.swift index dcb27929..bc53db71 100644 --- a/Core/Sources/HostApp/AccountSettings/CopilotView.swift +++ b/Core/Sources/HostApp/AccountSettings/CopilotView.swift @@ -2,6 +2,7 @@ import AppKit import Client import GitHubCopilotService import Preferences +import SharedUIComponents import SuggestionModel import SwiftUI @@ -32,7 +33,7 @@ struct CopilotView: View { @Published var installationStep: GitHubCopilotInstallationManager.InstallationStep? init() {} - + init( installationStatus: GitHubCopilotInstallationManager.InstallationStatus, installationStep: GitHubCopilotInstallationManager.InstallationStep? @@ -142,10 +143,23 @@ struct CopilotView: View { HStack { VStack(alignment: .leading, spacing: 8) { Form { - TextField(text: $settings.nodePath, prompt: Text("node")) { + TextField( + text: $settings.nodePath, + prompt: Text( + "node" + ) + ) { Text("Path to Node (v17+)") } + Text( + "Provide the path to the executable if it can't be found by the app, shim executable is not supported" + ) + .lineLimit(10) + .foregroundColor(.secondary) + .font(.callout) + .dynamicHeightTextInFormWorkaround() + Picker(selection: $settings.runNodeWith) { ForEach(NodeRunner.allCases, id: \.rawValue) { runner in switch runner { @@ -160,11 +174,28 @@ struct CopilotView: View { } label: { Text("Run Node with") } + + Group { + switch settings.runNodeWith { + case .env: + Text( + "PATH: `/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`" + ) + case .bash: + Text("PATH inherited from bash configurations.") + case .shell: + Text("PATH inherited from $SHELL configurations.") + } + } + .lineLimit(10) + .foregroundColor(.secondary) + .font(.callout) + .dynamicHeightTextInFormWorkaround() } - Text( - "You may have to restart the helper app to apply the changes. To do so, simply close the helper app by clicking on the menu bar icon that looks like a steer wheel, it will automatically restart as needed." - ) + Text(""" + You may have to restart the helper app to apply the changes. To do so, simply close the helper app by clicking on the menu bar icon that looks like a tentacle, it will automatically restart as needed. + """) .lineLimit(6) .fixedSize(horizontal: false, vertical: true) .foregroundColor(.secondary) @@ -236,9 +267,16 @@ struct CopilotView: View { Form { Toggle( - "Ignore Trailing New Lines and Whitespaces", + "Remove Extra New Lines Generated by GitHub Copilot", isOn: $settings.gitHubCopilotIgnoreTrailingNewLines ) + Text( + "Sometimes GitHub Copilot may generate extra unwanted new lines at the end of a suggestion. If you don't like that, you can turn this toggle on." + ) + .lineLimit(10) + .foregroundColor(.secondary) + .font(.callout) + .dynamicHeightTextInFormWorkaround() Toggle("Verbose Log", isOn: $settings.gitHubCopilotVerboseLog) } diff --git a/Core/Sources/Service/RealtimeSuggestionController.swift b/Core/Sources/Service/RealtimeSuggestionController.swift index 84a2ed5f..63ed9a38 100644 --- a/Core/Sources/Service/RealtimeSuggestionController.swift +++ b/Core/Sources/Service/RealtimeSuggestionController.swift @@ -12,7 +12,7 @@ import QuartzCore import Workspace import XcodeInspector -public class RealtimeSuggestionController { +public actor RealtimeSuggestionController { let eventObserver: CGEventObserverType = CGEventObserver(eventsOfInterest: [.keyDown]) private var task: Task? private var inflightPrefetchTask: Task? @@ -23,12 +23,13 @@ public class RealtimeSuggestionController { private var sourceEditor: SourceEditor? init() {} - + + nonisolated func start() { Task { [weak self] in if let app = ActiveApplicationMonitor.shared.activeXcode { - self?.handleXcodeChanged(app) - self?.startHIDObservation() + await self?.handleXcodeChanged(app) + await self?.startHIDObservation() } var previousApp = ActiveApplicationMonitor.shared.activeXcode for await app in ActiveApplicationMonitor.shared.createStream() { @@ -37,13 +38,13 @@ public class RealtimeSuggestionController { defer { previousApp = app } if let app = ActiveApplicationMonitor.shared.activeXcode, app != previousApp { - self.handleXcodeChanged(app) + await self.handleXcodeChanged(app) } if ActiveApplicationMonitor.shared.activeXcode != nil { - startHIDObservation() + await startHIDObservation() } else { - stopHIDObservation() + await stopHIDObservation() } } } @@ -85,7 +86,7 @@ public class RealtimeSuggestionController { for await _ in notifications { guard let self else { return } try Task.checkCancellation() - self.handleFocusElementChange() + await self.handleFocusElementChange() } } } @@ -112,7 +113,7 @@ public class RealtimeSuggestionController { editorObservationTask = Task { [weak self] in let fileURL = try await Environment.fetchCurrentFileURL() - if let sourceEditor = self?.sourceEditor { + if let sourceEditor = await self?.sourceEditor { await PseudoCommandHandler().invalidateRealtimeSuggestionsIfNeeded( fileURL: fileURL, sourceEditor: sourceEditor @@ -132,10 +133,10 @@ public class RealtimeSuggestionController { switch notification.name { case kAXValueChangedNotification: await cancelInFlightTasks() - self.triggerPrefetchDebounced() + await self.triggerPrefetchDebounced() await self.notifyEditingFileChange(editor: focusElement) case kAXSelectedTextChangedNotification: - guard let sourceEditor else { continue } + guard let sourceEditor = await sourceEditor else { continue } let fileURL = XcodeInspector.shared.activeDocumentURL await PseudoCommandHandler().invalidateRealtimeSuggestionsIfNeeded( fileURL: fileURL, diff --git a/Core/Sources/SuggestionWidget/ChatWindowView.swift b/Core/Sources/SuggestionWidget/ChatWindowView.swift index 247f8f00..b69cb619 100644 --- a/Core/Sources/SuggestionWidget/ChatWindowView.swift +++ b/Core/Sources/SuggestionWidget/ChatWindowView.swift @@ -333,8 +333,10 @@ struct ChatTabContainer: View { } else { ForEach(viewStore.state.tabInfo) { tabInfo in if let tab = chatTabPool.getTab(of: tabInfo.id) { + let isActive = tab.id == viewStore.state.selectedTabId tab.body - .opacity(tab.id == viewStore.state.selectedTabId ? 1 : 0) + .opacity(isActive ? 1 : 0) + .disabled(!isActive) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { EmptyView() diff --git a/Tool/Sources/SharedUIComponents/DynamicHeightTextInFormWorkaround.swift b/Tool/Sources/SharedUIComponents/DynamicHeightTextInFormWorkaround.swift new file mode 100644 index 00000000..4a5efd31 --- /dev/null +++ b/Tool/Sources/SharedUIComponents/DynamicHeightTextInFormWorkaround.swift @@ -0,0 +1,17 @@ +import SwiftUI + +struct DynamicHeightTextInFormWorkaroundModifier: ViewModifier { + func body(content: Content) -> some View { + HStack(spacing: 0) { + content + Spacer() + } + .fixedSize(horizontal: false, vertical: true) + } +} + +public extension View { + func dynamicHeightTextInFormWorkaround() -> some View { + modifier(DynamicHeightTextInFormWorkaroundModifier()) + } +} diff --git a/Version.xcconfig b/Version.xcconfig index f9aa5722..c5391dee 100644 --- a/Version.xcconfig +++ b/Version.xcconfig @@ -1,2 +1,2 @@ -APP_VERSION = 0.22.2 -APP_BUILD = 232 +APP_VERSION = 0.22.3 +APP_BUILD = 233 diff --git a/appcast.xml b/appcast.xml index 9ad33bc3..e915b77f 100644 --- a/appcast.xml +++ b/appcast.xml @@ -3,6 +3,18 @@ Copilot for Xcode + + 0.22.3 + Sat, 02 Sep 2023 15:51:16 +0800 + 233 + 0.22.3 + 12.0 + + https://github.com/intitni/CopilotForXcode/releases/tag/0.22.3 + + + + 0.22.2 Sat, 19 Aug 2023 10:48:38 +0800