From e271a7f0cc16779831b80d9fb169e73959245cb5 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 15:02:35 +0800 Subject: [PATCH 01/21] Bump Codeium to 1.6.6 --- Tool/Sources/CodeiumService/CodeiumInstallationManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tool/Sources/CodeiumService/CodeiumInstallationManager.swift b/Tool/Sources/CodeiumService/CodeiumInstallationManager.swift index 808aeabc..62e6e0b5 100644 --- a/Tool/Sources/CodeiumService/CodeiumInstallationManager.swift +++ b/Tool/Sources/CodeiumService/CodeiumInstallationManager.swift @@ -3,7 +3,7 @@ import Terminal public struct CodeiumInstallationManager { private static var isInstalling = false - static let latestSupportedVersion = "1.4.15" + static let latestSupportedVersion = "1.6.6" public init() {} From 02a2f00176f6fb2e2e83dc76897d1399e0845d34 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 15:02:51 +0800 Subject: [PATCH 02/21] Bump GitHub Copilot to 1.12.1 --- .../GitHubCopilotService/GitHubCopilotInstallationManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tool/Sources/GitHubCopilotService/GitHubCopilotInstallationManager.swift b/Tool/Sources/GitHubCopilotService/GitHubCopilotInstallationManager.swift index 051a3425..c706f81f 100644 --- a/Tool/Sources/GitHubCopilotService/GitHubCopilotInstallationManager.swift +++ b/Tool/Sources/GitHubCopilotService/GitHubCopilotInstallationManager.swift @@ -10,7 +10,7 @@ public struct GitHubCopilotInstallationManager { return URL(string: link)! } - static let latestSupportedVersion = "1.11.4" + static let latestSupportedVersion = "1.12.1" public init() {} From c91bdf140ad8f195a09cf8de7930c47a064f6063 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 15:06:39 +0800 Subject: [PATCH 03/21] Fix that changes to files in gitignore were notified to the suggestion services --- Tool/Sources/Workspace/Filespace.swift | 2 ++ .../WorkspaceSuggestionService/SuggestionWorkspacePlugin.swift | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Tool/Sources/Workspace/Filespace.swift b/Tool/Sources/Workspace/Filespace.swift index 30553332..685fd134 100644 --- a/Tool/Sources/Workspace/Filespace.swift +++ b/Tool/Sources/Workspace/Filespace.swift @@ -88,7 +88,9 @@ public final class Filespace { // MARK: Git Ignore + @WorkspaceActor private var gitIgnoreStatus: GitIgnoreStatus? + @WorkspaceActor public var isGitIgnored: Bool { get async { @Dependency(\.gitIgnoredChecker) var gitIgnoredChecker diff --git a/Tool/Sources/WorkspaceSuggestionService/SuggestionWorkspacePlugin.swift b/Tool/Sources/WorkspaceSuggestionService/SuggestionWorkspacePlugin.swift index 7d17b684..e9147a79 100644 --- a/Tool/Sources/WorkspaceSuggestionService/SuggestionWorkspacePlugin.swift +++ b/Tool/Sources/WorkspaceSuggestionService/SuggestionWorkspacePlugin.swift @@ -88,6 +88,7 @@ public final class SuggestionServiceWorkspacePlugin: WorkspacePlugin { workspace?.refreshUpdateTime() workspace?.openedFileRecoverableStorage.openFile(fileURL: filespace.fileURL) Task { + guard !(await filespace.isGitIgnored) else { return } // check if file size is larger than 15MB, if so, return immediately if let attrs = try? FileManager.default .attributesOfItem(atPath: filespace.fileURL.path), @@ -106,6 +107,7 @@ public final class SuggestionServiceWorkspacePlugin: WorkspacePlugin { filespace.refreshUpdateTime() workspace?.refreshUpdateTime() Task { + guard !(await filespace.isGitIgnored) else { return } try await suggestionService?.notifyChangeTextDocument( fileURL: filespace.fileURL, content: content @@ -117,6 +119,7 @@ public final class SuggestionServiceWorkspacePlugin: WorkspacePlugin { filespace.refreshUpdateTime() workspace?.refreshUpdateTime() Task { + guard !(await filespace.isGitIgnored) else { return } try await suggestionService?.notifySaveTextDocument(fileURL: filespace.fileURL) } } From 92020915eb41cbec32d8bdebbebd3b25d3b8ed4c Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 17:11:49 +0800 Subject: [PATCH 04/21] Format file --- .../FeatureReducers/WidgetFeature.swift | 157 +++++++++--------- 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift index 3465e422..89095dc2 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift @@ -14,7 +14,7 @@ public struct WidgetFeature: ReducerProtocol { var alphaValue: Double = 0 var frame: CGRect = .zero } - + public struct Windows: Equatable { public var widgetWindowState = WindowState() public var chatWindowState = WindowState() @@ -22,32 +22,32 @@ public struct WidgetFeature: ReducerProtocol { public var sharedPanelWindowState = WindowState() public var tabWindowState = WindowState() } - + public enum WindowCanBecomeKey: Equatable { case sharedPanel case chatPanel } - + public struct State: Equatable { var focusingDocumentURL: URL? public var colorScheme: ColorScheme = .light - + // MARK: Panels - + public var panelState = PanelFeature.State() - + // MARK: ChatPanel - + public var chatPanelState = ChatPanelFeature.State() - + // MARK: CircularWidget - + public struct CircularWidgetState: Equatable { var isProcessingCounters = [CircularWidgetFeature.IsProcessingCounter]() var isProcessing: Bool = false var animationProgress: Double = 0 } - + public var circularWidgetState = CircularWidgetState() var _circularWidgetState: CircularWidgetFeature.State { get { @@ -71,7 +71,7 @@ public struct WidgetFeature: ReducerProtocol { return false }(), isContentEmpty: chatPanelState.chatTabGroup.tabInfo.isEmpty - && panelState.sharedPanelState.isEmpty, + && panelState.sharedPanelState.isEmpty, isChatPanelDetached: chatPanelState.chatPanelInASeparateWindow, isChatOpen: chatPanelState.isPanelDisplayed, animationProgress: circularWidgetState.animationProgress @@ -85,12 +85,12 @@ public struct WidgetFeature: ReducerProtocol { ) } } - + var lastUpdateWindowOpacityTime = Date(timeIntervalSince1970: 0) - + public init() {} } - + private enum CancelID { case observeActiveApplicationChange case observeCompletionPanelChange @@ -99,7 +99,7 @@ public struct WidgetFeature: ReducerProtocol { case observeEditorChange case observeUserDefaults } - + public enum Action: Equatable { case startup case observeActiveApplicationChange @@ -107,28 +107,28 @@ public struct WidgetFeature: ReducerProtocol { case observeFullscreenChange case observeColorSchemeChange case observePresentationModeChange - + case observeWindowChange case observeEditorChange - + case updateActiveApplication case updateColorScheme - + case updateWindowLocation(animated: Bool) case updateWindowOpacity case updateFocusingDocumentURL case updateWindowOpacityFinished case updateKeyWindow(WindowCanBecomeKey) - + case panel(PanelFeature.Action) case chatPanel(ChatPanelFeature.Action) case circularWidget(CircularWidgetFeature.Action) } - + var windows: WidgetWindows { suggestionWidgetControllerDependency.windows } - + @Dependency(\.suggestionWidgetUserDefaultsObservers) var userDefaultsObservers @Dependency(\.suggestionWidgetControllerDependency) var suggestionWidgetControllerDependency @Dependency(\.activeApplicationMonitor) var activeApplicationMonitor @@ -136,25 +136,25 @@ public struct WidgetFeature: ReducerProtocol { @Dependency(\.mainQueue) var mainQueue @Dependency(\.activateThisApp) var activateThisApp @Dependency(\.activatePreviousActiveApp) var activatePreviousActiveApp - + public enum DebounceKey: Hashable { case updateWindowOpacity } - + public init() {} - + public var body: some ReducerProtocol { Scope(state: \._circularWidgetState, action: /Action.circularWidget) { CircularWidgetFeature() } - + Reduce { state, action in switch action { case .circularWidget(.detachChatPanelToggleClicked): return .run { send in await send(.chatPanel(.toggleChatPanelDetachedButtonClicked)) } - + case .circularWidget(.widgetClicked): let wasDisplayingContent = state._circularWidgetState.isDisplayingContent if wasDisplayingContent { @@ -166,12 +166,12 @@ public struct WidgetFeature: ReducerProtocol { state.panelState.suggestionPanelState.isPanelDisplayed = true state.chatPanelState.isPanelDisplayed = true } - + let isDisplayingContent = state._circularWidgetState.isDisplayingContent let hasChat = state.chatPanelState.chatTabGroup.selectedTabInfo != nil let hasPromptToCode = state.panelState.sharedPanelState.content .promptToCodeGroup.activePromptToCode != nil - + return .run { send in if isDisplayingContent { if hasPromptToCode { @@ -181,26 +181,26 @@ public struct WidgetFeature: ReducerProtocol { } await send(.chatPanel(.focusActiveChatTab)) } - + if isDisplayingContent, !(await NSApplication.shared.isActive) { activateThisApp() } else if !isDisplayingContent { activatePreviousActiveApp() } } - + default: return .none } } - + Scope(state: \.panelState, action: /Action.panel) { PanelFeature() } - + Scope(state: \.chatPanelState, action: /Action.chatPanel) { ChatPanelFeature() } - + Reduce { state, action in switch action { case .chatPanel(.presentChatPanel): @@ -223,7 +223,7 @@ public struct WidgetFeature: ReducerProtocol { default: return .none } } - + Reduce { state, action in switch action { case .startup: @@ -234,7 +234,7 @@ public struct WidgetFeature: ReducerProtocol { .run { send in await send(.observeColorSchemeChange) }, .run { send in await send(.observePresentationModeChange) } ) - + case .observeActiveApplicationChange: return .run { send in var previousApp: RunningApplicationInfo? @@ -246,7 +246,7 @@ public struct WidgetFeature: ReducerProtocol { previousApp = app } }.cancellable(id: CancelID.observeActiveApplicationChange, cancelInFlight: true) - + case .observeCompletionPanelChange: return .run { send in let stream = AsyncStream { continuation in @@ -272,7 +272,7 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowOpacity) } }.cancellable(id: CancelID.observeCompletionPanelChange, cancelInFlight: true) - + case .observeFullscreenChange: return .run { _ in let sequence = NSWorkspace.shared.notificationCenter @@ -288,7 +288,7 @@ public struct WidgetFeature: ReducerProtocol { } } }.cancellable(id: CancelID.observeFullscreenChange, cancelInFlight: true) - + case .observeColorSchemeChange: return .run { send in await send(.updateColorScheme) @@ -296,23 +296,23 @@ public struct WidgetFeature: ReducerProtocol { userDefaultsObservers.colorSchemeChangeObserver.onChange = { continuation.yield() } - + userDefaultsObservers.systemColorSchemeChangeObserver.onChange = { continuation.yield() } - + continuation.onTermination = { _ in userDefaultsObservers.colorSchemeChangeObserver.onChange = {} userDefaultsObservers.systemColorSchemeChangeObserver.onChange = {} } } - + for await _ in stream { try Task.checkCancellation() await send(.updateColorScheme) } }.cancellable(id: CancelID.observeUserDefaults, cancelInFlight: true) - + case .observePresentationModeChange: return .run { send in await send(.updateColorScheme) @@ -320,28 +320,28 @@ public struct WidgetFeature: ReducerProtocol { userDefaultsObservers.presentationModeChangeObserver.onChange = { continuation.yield() } - + continuation.onTermination = { _ in userDefaultsObservers.presentationModeChangeObserver.onChange = {} } } - + for await _ in stream { try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) } }.cancellable(id: CancelID.observeUserDefaults, cancelInFlight: true) - + case .observeWindowChange: guard let app = activeApplicationMonitor.activeApplication else { return .none } guard app.isXcode else { return .none } - + let documentURL = state.focusingDocumentURL - + let notifications = AXNotificationStream( app: app, notificationNames: - kAXApplicationActivatedNotification, + kAXApplicationActivatedNotification, kAXMovedNotification, kAXResizedNotification, kAXMainWindowChangedNotification, @@ -352,14 +352,14 @@ public struct WidgetFeature: ReducerProtocol { kAXWindowMiniaturizedNotification, kAXWindowDeminiaturizedNotification ) - + return .run { send in await send(.observeEditorChange) await send(.panel(.switchToAnotherEditorAndUpdateContent)) - + for await notification in notifications { try Task.checkCancellation() - + // Hide the widgets before switching to another window/editor // so the transition looks better. if [ @@ -373,7 +373,7 @@ public struct WidgetFeature: ReducerProtocol { } await send(.updateFocusingDocumentURL) } - + // update widgets. if [ kAXFocusedUIElementChangedNotification, @@ -391,7 +391,7 @@ public struct WidgetFeature: ReducerProtocol { } } }.cancellable(id: CancelID.observeWindowChange, cancelInFlight: true) - + case .observeEditorChange: guard let app = activeApplicationMonitor.activeApplication else { return .none } let appElement = AXUIElementCreateApplication(app.processIdentifier) @@ -400,7 +400,7 @@ public struct WidgetFeature: ReducerProtocol { let scrollView = focusedElement.parent, let scrollBar = scrollView.verticalScrollBar else { return .none } - + let selectionRangeChange = AXNotificationStream( app: app, element: focusedElement, @@ -411,7 +411,7 @@ public struct WidgetFeature: ReducerProtocol { element: scrollBar, notificationNames: kAXValueChangedNotification ) - + return .run { send in if #available(macOS 13.0, *) { for await _ in merge( @@ -433,9 +433,9 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowOpacity) } } - + }.cancellable(id: CancelID.observeEditorChange, cancelInFlight: true) - + case .updateActiveApplication: if let app = activeApplicationMonitor.activeApplication, app.isXcode { return .run { send in @@ -450,13 +450,13 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowLocation(animated: false)) await send(.updateWindowOpacity) } - + case .updateColorScheme: let widgetColorScheme = UserDefaults.shared.value(for: \.widgetColorScheme) let systemColorScheme: ColorScheme = NSApp.effectiveAppearance.name == .darkAqua - ? .dark - : .light - + ? .dark + : .light + let scheme: ColorScheme = { switch (widgetColorScheme, systemColorScheme) { case (.system, .dark), (.dark, _): @@ -467,24 +467,23 @@ public struct WidgetFeature: ReducerProtocol { return .light } }() - + state.colorScheme = scheme state.panelState.sharedPanelState.colorScheme = scheme state.panelState.suggestionPanelState.colorScheme = scheme state.chatPanelState.colorScheme = scheme return .none - + case .updateFocusingDocumentURL: state.focusingDocumentURL = xcodeInspector.realtimeActiveDocumentURL return .none - - #warning("TODO: use function instead of action for high rate actions like this") + case let .updateWindowLocation(animated): guard let widgetLocation = generateWidgetLocation() else { return .none } state.panelState.sharedPanelState.alignTopToAnchor = widgetLocation .defaultPanelLocation .alignPanelTop - + if let suggestionPanelLocation = widgetLocation.suggestionPanelLocation { state.panelState.suggestionPanelState.isPanelOutOfFrame = false state.panelState.suggestionPanelState @@ -493,9 +492,9 @@ public struct WidgetFeature: ReducerProtocol { } else { state.panelState.suggestionPanelState.isPanelOutOfFrame = true } - + let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow - + return .run { _ in Task { @MainActor in windows.widgetWindow.setFrame( @@ -513,7 +512,7 @@ public struct WidgetFeature: ReducerProtocol { display: false, animate: animated ) - + if let suggestionPanelLocation = widgetLocation.suggestionPanelLocation { windows.suggestionPanelWindow.setFrame( suggestionPanelLocation.frame, @@ -521,7 +520,7 @@ public struct WidgetFeature: ReducerProtocol { animate: animated ) } - + if isChatPanelDetached { if windows.chatPanelWindow.alphaValue == 0 { windows.chatPanelWindow.setFrame( @@ -539,7 +538,7 @@ public struct WidgetFeature: ReducerProtocol { } } } - + case .updateWindowOpacity: let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow let hasChat = !state.chatPanelState.chatTabGroup.tabInfo.isEmpty @@ -558,7 +557,7 @@ public struct WidgetFeature: ReducerProtocol { windows.suggestionPanelWindow.alphaValue = noFocus ? 0 : 1 windows.widgetWindow.alphaValue = noFocus ? 0 : 1 windows.tabWindow.alphaValue = 0 - + if isChatPanelDetached { windows.chatPanelWindow.alphaValue = hasChat ? 1 : 0 } else { @@ -577,7 +576,7 @@ public struct WidgetFeature: ReducerProtocol { } return true }() - + windows.sharedPanelWindow.alphaValue = noFocus ? 0 : 1 windows.suggestionPanelWindow.alphaValue = noFocus ? 0 : 1 windows.widgetWindow.alphaValue = noFocus ? 0 : 1 @@ -602,11 +601,11 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowOpacityFinished) } .cancellable(id: DebounceKey.updateWindowOpacity, cancelInFlight: true) - + case .updateWindowOpacityFinished: state.lastUpdateWindowOpacityTime = Date() return .none - + case let .updateKeyWindow(window): return .run { _ in switch window { @@ -616,19 +615,21 @@ public struct WidgetFeature: ReducerProtocol { await windows.sharedPanelWindow.makeKeyAndOrderFront(nil) } } - + case .circularWidget: return .none - + case .panel: return .none - + case .chatPanel: return .none } } } +} +extension WidgetFeature { @MainActor func hidePanelWindows() { windows.sharedPanelWindow.alphaValue = 0 From 04376db00e09fe570825708665f4dde73987c843 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 17:12:13 +0800 Subject: [PATCH 05/21] Make corrupted license key deactivated instead of returning nil --- Pro | 2 +- Tool/Sources/Logger/Logger.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Pro b/Pro index a33512ff..2c26e3ac 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit a33512ffb46a72b4fee6d89d397560c28f536e79 +Subproject commit 2c26e3acfee6cca0a563a8ea58c078b8154a3ffc diff --git a/Tool/Sources/Logger/Logger.swift b/Tool/Sources/Logger/Logger.swift index 8b740ddc..00b1d6eb 100644 --- a/Tool/Sources/Logger/Logger.swift +++ b/Tool/Sources/Logger/Logger.swift @@ -20,6 +20,7 @@ public final class Logger { public static let codeium = Logger(category: "Codeium") public static let langchain = Logger(category: "LangChain") public static let retrieval = Logger(category: "Retrieval") + public static let license = Logger(category: "License") #if DEBUG /// Use a temp logger to log something temporary. I won't be available in release builds. public static let temp = Logger(category: "Temp") From 0c0359231eb10d185b094c92c88e916fe2fa415f Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 21:21:47 +0800 Subject: [PATCH 06/21] Update --- Pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pro b/Pro index 2c26e3ac..9b000a35 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit 2c26e3acfee6cca0a563a8ea58c078b8154a3ffc +Subproject commit 9b000a357b3e5679ee004fd1cc9937781f217926 From da799f13bd408a8c31b280dded58ec166ee6ba9d Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 22:06:32 +0800 Subject: [PATCH 07/21] Replace activeApplicationMonitor with xcodeInspector --- .../FeatureReducers/WidgetFeature.swift | 213 ++++++++++-------- 1 file changed, 113 insertions(+), 100 deletions(-) diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift index 89095dc2..3912eb43 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift @@ -14,7 +14,7 @@ public struct WidgetFeature: ReducerProtocol { var alphaValue: Double = 0 var frame: CGRect = .zero } - + public struct Windows: Equatable { public var widgetWindowState = WindowState() public var chatWindowState = WindowState() @@ -22,32 +22,32 @@ public struct WidgetFeature: ReducerProtocol { public var sharedPanelWindowState = WindowState() public var tabWindowState = WindowState() } - + public enum WindowCanBecomeKey: Equatable { case sharedPanel case chatPanel } - + public struct State: Equatable { var focusingDocumentURL: URL? public var colorScheme: ColorScheme = .light - + // MARK: Panels - + public var panelState = PanelFeature.State() - + // MARK: ChatPanel - + public var chatPanelState = ChatPanelFeature.State() - + // MARK: CircularWidget - + public struct CircularWidgetState: Equatable { var isProcessingCounters = [CircularWidgetFeature.IsProcessingCounter]() var isProcessing: Bool = false var animationProgress: Double = 0 } - + public var circularWidgetState = CircularWidgetState() var _circularWidgetState: CircularWidgetFeature.State { get { @@ -71,7 +71,7 @@ public struct WidgetFeature: ReducerProtocol { return false }(), isContentEmpty: chatPanelState.chatTabGroup.tabInfo.isEmpty - && panelState.sharedPanelState.isEmpty, + && panelState.sharedPanelState.isEmpty, isChatPanelDetached: chatPanelState.chatPanelInASeparateWindow, isChatOpen: chatPanelState.isPanelDisplayed, animationProgress: circularWidgetState.animationProgress @@ -85,12 +85,12 @@ public struct WidgetFeature: ReducerProtocol { ) } } - + var lastUpdateWindowOpacityTime = Date(timeIntervalSince1970: 0) - + public init() {} } - + private enum CancelID { case observeActiveApplicationChange case observeCompletionPanelChange @@ -99,7 +99,7 @@ public struct WidgetFeature: ReducerProtocol { case observeEditorChange case observeUserDefaults } - + public enum Action: Equatable { case startup case observeActiveApplicationChange @@ -107,54 +107,53 @@ public struct WidgetFeature: ReducerProtocol { case observeFullscreenChange case observeColorSchemeChange case observePresentationModeChange - + case observeWindowChange case observeEditorChange - + case updateActiveApplication case updateColorScheme - + case updateWindowLocation(animated: Bool) case updateWindowOpacity case updateFocusingDocumentURL case updateWindowOpacityFinished case updateKeyWindow(WindowCanBecomeKey) - + case panel(PanelFeature.Action) case chatPanel(ChatPanelFeature.Action) case circularWidget(CircularWidgetFeature.Action) } - + var windows: WidgetWindows { suggestionWidgetControllerDependency.windows } - + @Dependency(\.suggestionWidgetUserDefaultsObservers) var userDefaultsObservers @Dependency(\.suggestionWidgetControllerDependency) var suggestionWidgetControllerDependency - @Dependency(\.activeApplicationMonitor) var activeApplicationMonitor @Dependency(\.xcodeInspector) var xcodeInspector @Dependency(\.mainQueue) var mainQueue @Dependency(\.activateThisApp) var activateThisApp @Dependency(\.activatePreviousActiveApp) var activatePreviousActiveApp - + public enum DebounceKey: Hashable { case updateWindowOpacity } - + public init() {} - + public var body: some ReducerProtocol { Scope(state: \._circularWidgetState, action: /Action.circularWidget) { CircularWidgetFeature() } - + Reduce { state, action in switch action { case .circularWidget(.detachChatPanelToggleClicked): return .run { send in await send(.chatPanel(.toggleChatPanelDetachedButtonClicked)) } - + case .circularWidget(.widgetClicked): let wasDisplayingContent = state._circularWidgetState.isDisplayingContent if wasDisplayingContent { @@ -166,12 +165,12 @@ public struct WidgetFeature: ReducerProtocol { state.panelState.suggestionPanelState.isPanelDisplayed = true state.chatPanelState.isPanelDisplayed = true } - + let isDisplayingContent = state._circularWidgetState.isDisplayingContent let hasChat = state.chatPanelState.chatTabGroup.selectedTabInfo != nil let hasPromptToCode = state.panelState.sharedPanelState.content .promptToCodeGroup.activePromptToCode != nil - + return .run { send in if isDisplayingContent { if hasPromptToCode { @@ -181,26 +180,26 @@ public struct WidgetFeature: ReducerProtocol { } await send(.chatPanel(.focusActiveChatTab)) } - + if isDisplayingContent, !(await NSApplication.shared.isActive) { activateThisApp() } else if !isDisplayingContent { activatePreviousActiveApp() } } - + default: return .none } } - + Scope(state: \.panelState, action: /Action.panel) { PanelFeature() } - + Scope(state: \.chatPanelState, action: /Action.chatPanel) { ChatPanelFeature() } - + Reduce { state, action in switch action { case .chatPanel(.presentChatPanel): @@ -223,7 +222,7 @@ public struct WidgetFeature: ReducerProtocol { default: return .none } } - + Reduce { state, action in switch action { case .startup: @@ -234,19 +233,29 @@ public struct WidgetFeature: ReducerProtocol { .run { send in await send(.observeColorSchemeChange) }, .run { send in await send(.observePresentationModeChange) } ) - + case .observeActiveApplicationChange: return .run { send in - var previousApp: RunningApplicationInfo? - for await app in activeApplicationMonitor.createInfoStream() { + let stream = AsyncStream { continuation in + let cancellable = xcodeInspector.$activeApplication.sink { newValue in + guard let newValue else { return } + continuation.yield(newValue.runningApplication) + } + continuation.onTermination = { _ in + cancellable.cancel() + } + } + + var previousAppIdentifier: pid_t? + for await app in stream { try Task.checkCancellation() - if app?.processIdentifier != previousApp?.processIdentifier { + if app.processIdentifier != previousAppIdentifier { await send(.updateActiveApplication) } - previousApp = app + previousAppIdentifier = app.processIdentifier } }.cancellable(id: CancelID.observeActiveApplicationChange, cancelInFlight: true) - + case .observeCompletionPanelChange: return .run { send in let stream = AsyncStream { continuation in @@ -272,23 +281,24 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowOpacity) } }.cancellable(id: CancelID.observeCompletionPanelChange, cancelInFlight: true) - + case .observeFullscreenChange: return .run { _ in let sequence = NSWorkspace.shared.notificationCenter .notifications(named: NSWorkspace.activeSpaceDidChangeNotification) for await _ in sequence { try Task.checkCancellation() - guard let activeXcode = activeApplicationMonitor.activeXcode - else { continue } + guard let activeXcode = xcodeInspector.activeXcode else { continue } guard await windows.fullscreenDetector.isOnActiveSpace else { continue } - let app = AXUIElementCreateApplication(activeXcode.processIdentifier) + let app = AXUIElementCreateApplication( + activeXcode.runningApplication.processIdentifier + ) if let _ = app.focusedWindow { await windows.orderFront() } } }.cancellable(id: CancelID.observeFullscreenChange, cancelInFlight: true) - + case .observeColorSchemeChange: return .run { send in await send(.updateColorScheme) @@ -296,23 +306,23 @@ public struct WidgetFeature: ReducerProtocol { userDefaultsObservers.colorSchemeChangeObserver.onChange = { continuation.yield() } - + userDefaultsObservers.systemColorSchemeChangeObserver.onChange = { continuation.yield() } - + continuation.onTermination = { _ in userDefaultsObservers.colorSchemeChangeObserver.onChange = {} userDefaultsObservers.systemColorSchemeChangeObserver.onChange = {} } } - + for await _ in stream { try Task.checkCancellation() await send(.updateColorScheme) } }.cancellable(id: CancelID.observeUserDefaults, cancelInFlight: true) - + case .observePresentationModeChange: return .run { send in await send(.updateColorScheme) @@ -320,28 +330,28 @@ public struct WidgetFeature: ReducerProtocol { userDefaultsObservers.presentationModeChangeObserver.onChange = { continuation.yield() } - + continuation.onTermination = { _ in userDefaultsObservers.presentationModeChangeObserver.onChange = {} } } - + for await _ in stream { try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) } }.cancellable(id: CancelID.observeUserDefaults, cancelInFlight: true) - + case .observeWindowChange: - guard let app = activeApplicationMonitor.activeApplication else { return .none } + guard let app = xcodeInspector.activeApplication else { return .none } guard app.isXcode else { return .none } - + let documentURL = state.focusingDocumentURL - + let notifications = AXNotificationStream( - app: app, + app: app.runningApplication, notificationNames: - kAXApplicationActivatedNotification, + kAXApplicationActivatedNotification, kAXMovedNotification, kAXResizedNotification, kAXMainWindowChangedNotification, @@ -352,14 +362,14 @@ public struct WidgetFeature: ReducerProtocol { kAXWindowMiniaturizedNotification, kAXWindowDeminiaturizedNotification ) - + return .run { send in await send(.observeEditorChange) await send(.panel(.switchToAnotherEditorAndUpdateContent)) - + for await notification in notifications { try Task.checkCancellation() - + // Hide the widgets before switching to another window/editor // so the transition looks better. if [ @@ -373,7 +383,7 @@ public struct WidgetFeature: ReducerProtocol { } await send(.updateFocusingDocumentURL) } - + // update widgets. if [ kAXFocusedUIElementChangedNotification, @@ -391,53 +401,53 @@ public struct WidgetFeature: ReducerProtocol { } } }.cancellable(id: CancelID.observeWindowChange, cancelInFlight: true) - + case .observeEditorChange: - guard let app = activeApplicationMonitor.activeApplication else { return .none } - let appElement = AXUIElementCreateApplication(app.processIdentifier) + guard let app = xcodeInspector.activeApplication else { return .none } + let appElement = AXUIElementCreateApplication( + app.runningApplication.processIdentifier + ) guard let focusedElement = appElement.focusedElement, focusedElement.description == "Source Editor", let scrollView = focusedElement.parent, let scrollBar = scrollView.verticalScrollBar else { return .none } - + let selectionRangeChange = AXNotificationStream( - app: app, + app: app.runningApplication, element: focusedElement, notificationNames: kAXSelectedTextChangedNotification ) let scroll = AXNotificationStream( - app: app, + app: app.runningApplication, element: scrollBar, notificationNames: kAXValueChangedNotification ) - + return .run { send in if #available(macOS 13.0, *) { for await _ in merge( selectionRangeChange.debounce(for: Duration.milliseconds(500)), scroll ) { - guard activeApplicationMonitor.latestXcode != nil - else { return } + guard xcodeInspector.latestActiveXcode != nil else { return } try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) await send(.updateWindowOpacity) } } else { for await _ in merge(selectionRangeChange, scroll) { - guard activeApplicationMonitor.latestXcode != nil - else { return } + guard xcodeInspector.latestActiveXcode != nil else { return } try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) await send(.updateWindowOpacity) } } - + }.cancellable(id: CancelID.observeEditorChange, cancelInFlight: true) - + case .updateActiveApplication: - if let app = activeApplicationMonitor.activeApplication, app.isXcode { + if let app = xcodeInspector.activeApplication, app.isXcode { return .run { send in await send(.panel(.switchToAnotherEditorAndUpdateContent)) await send(.updateWindowLocation(animated: false)) @@ -450,13 +460,13 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowLocation(animated: false)) await send(.updateWindowOpacity) } - + case .updateColorScheme: let widgetColorScheme = UserDefaults.shared.value(for: \.widgetColorScheme) let systemColorScheme: ColorScheme = NSApp.effectiveAppearance.name == .darkAqua - ? .dark - : .light - + ? .dark + : .light + let scheme: ColorScheme = { switch (widgetColorScheme, systemColorScheme) { case (.system, .dark), (.dark, _): @@ -467,23 +477,23 @@ public struct WidgetFeature: ReducerProtocol { return .light } }() - + state.colorScheme = scheme state.panelState.sharedPanelState.colorScheme = scheme state.panelState.suggestionPanelState.colorScheme = scheme state.chatPanelState.colorScheme = scheme return .none - + case .updateFocusingDocumentURL: state.focusingDocumentURL = xcodeInspector.realtimeActiveDocumentURL return .none - + case let .updateWindowLocation(animated): guard let widgetLocation = generateWidgetLocation() else { return .none } state.panelState.sharedPanelState.alignTopToAnchor = widgetLocation .defaultPanelLocation .alignPanelTop - + if let suggestionPanelLocation = widgetLocation.suggestionPanelLocation { state.panelState.suggestionPanelState.isPanelOutOfFrame = false state.panelState.suggestionPanelState @@ -492,9 +502,9 @@ public struct WidgetFeature: ReducerProtocol { } else { state.panelState.suggestionPanelState.isPanelOutOfFrame = true } - + let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow - + return .run { _ in Task { @MainActor in windows.widgetWindow.setFrame( @@ -512,7 +522,7 @@ public struct WidgetFeature: ReducerProtocol { display: false, animate: animated ) - + if let suggestionPanelLocation = widgetLocation.suggestionPanelLocation { windows.suggestionPanelWindow.setFrame( suggestionPanelLocation.frame, @@ -520,7 +530,7 @@ public struct WidgetFeature: ReducerProtocol { animate: animated ) } - + if isChatPanelDetached { if windows.chatPanelWindow.alphaValue == 0 { windows.chatPanelWindow.setFrame( @@ -538,7 +548,7 @@ public struct WidgetFeature: ReducerProtocol { } } } - + case .updateWindowOpacity: let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow let hasChat = !state.chatPanelState.chatTabGroup.tabInfo.isEmpty @@ -549,22 +559,25 @@ public struct WidgetFeature: ReducerProtocol { } try Task.checkCancellation() let task = Task { @MainActor in - if let app = activeApplicationMonitor.activeApplication, app.isXcode { - let application = AXUIElementCreateApplication(app.processIdentifier) + if let app = xcodeInspector.activeApplication, app.isXcode { + let application = AXUIElementCreateApplication( + app.runningApplication.processIdentifier + ) /// We need this to hide the windows when Xcode is minimized. let noFocus = application.focusedWindow == nil windows.sharedPanelWindow.alphaValue = noFocus ? 0 : 1 windows.suggestionPanelWindow.alphaValue = noFocus ? 0 : 1 windows.widgetWindow.alphaValue = noFocus ? 0 : 1 windows.tabWindow.alphaValue = 0 - + if isChatPanelDetached { windows.chatPanelWindow.alphaValue = hasChat ? 1 : 0 } else { windows.chatPanelWindow.alphaValue = noFocus ? 0 : 1 } - } else if let app = activeApplicationMonitor.activeApplication, - app.bundleIdentifier == Bundle.main.bundleIdentifier + } else if + let app = xcodeInspector.activeApplication, + app.runningApplication.bundleIdentifier == Bundle.main.bundleIdentifier { let noFocus = { guard let xcode = xcodeInspector.latestActiveXcode @@ -576,7 +589,7 @@ public struct WidgetFeature: ReducerProtocol { } return true }() - + windows.sharedPanelWindow.alphaValue = noFocus ? 0 : 1 windows.suggestionPanelWindow.alphaValue = noFocus ? 0 : 1 windows.widgetWindow.alphaValue = noFocus ? 0 : 1 @@ -601,11 +614,11 @@ public struct WidgetFeature: ReducerProtocol { await send(.updateWindowOpacityFinished) } .cancellable(id: DebounceKey.updateWindowOpacity, cancelInFlight: true) - + case .updateWindowOpacityFinished: state.lastUpdateWindowOpacityTime = Date() return .none - + case let .updateKeyWindow(window): return .run { _ in switch window { @@ -615,13 +628,13 @@ public struct WidgetFeature: ReducerProtocol { await windows.sharedPanelWindow.makeKeyAndOrderFront(nil) } } - + case .circularWidget: return .none - + case .panel: return .none - + case .chatPanel: return .none } From 6f2fa5d79651d0d5397c3385a3037579a98e4741 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 22:11:53 +0800 Subject: [PATCH 08/21] Tweak updateWindowOpacity --- .../FeatureReducers/WidgetFeature.swift | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift index 3912eb43..7878379a 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift @@ -115,7 +115,7 @@ public struct WidgetFeature: ReducerProtocol { case updateColorScheme case updateWindowLocation(animated: Bool) - case updateWindowOpacity + case updateWindowOpacity(immediately: Bool) case updateFocusingDocumentURL case updateWindowOpacityFinished case updateKeyWindow(WindowCanBecomeKey) @@ -206,7 +206,7 @@ public struct WidgetFeature: ReducerProtocol { let isDetached = state.chatPanelState.chatPanelInASeparateWindow return .run { send in await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) if isDetached { Task { @MainActor in windows.chatPanelWindow.alphaValue = 1 @@ -217,7 +217,7 @@ public struct WidgetFeature: ReducerProtocol { let isDetached = state.chatPanelState.chatPanelInASeparateWindow return .run { send in await send(.updateWindowLocation(animated: !isDetached)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) } default: return .none } @@ -278,7 +278,7 @@ public struct WidgetFeature: ReducerProtocol { for await _ in stream { try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) } }.cancellable(id: CancelID.observeCompletionPanelChange, cancelInFlight: true) @@ -392,12 +392,12 @@ public struct WidgetFeature: ReducerProtocol { kAXFocusedWindowChangedNotification, ].contains(notification.name) { await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) await send(.observeEditorChange) await send(.panel(.switchToAnotherEditorAndUpdateContent)) } else { await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) } } }.cancellable(id: CancelID.observeWindowChange, cancelInFlight: true) @@ -433,14 +433,14 @@ public struct WidgetFeature: ReducerProtocol { guard xcodeInspector.latestActiveXcode != nil else { return } try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) } } else { for await _ in merge(selectionRangeChange, scroll) { guard xcodeInspector.latestActiveXcode != nil else { return } try Task.checkCancellation() await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: false)) } } @@ -451,14 +451,14 @@ public struct WidgetFeature: ReducerProtocol { return .run { send in await send(.panel(.switchToAnotherEditorAndUpdateContent)) await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: true)) await windows.orderFront() await send(.observeWindowChange) } } return .run { send in await send(.updateWindowLocation(animated: false)) - await send(.updateWindowOpacity) + await send(.updateWindowOpacity(immediately: true)) } case .updateColorScheme: @@ -549,10 +549,11 @@ public struct WidgetFeature: ReducerProtocol { } } - case .updateWindowOpacity: + case let .updateWindowOpacity(immediately): let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow let hasChat = !state.chatPanelState.chatTabGroup.tabInfo.isEmpty - let shouldDebounce = Date().timeIntervalSince(state.lastUpdateWindowOpacityTime) < 1 + let shouldDebounce = !immediately && + Date().timeIntervalSince(state.lastUpdateWindowOpacityTime) < 1 return .run { send in if shouldDebounce { try await mainQueue.sleep(for: .seconds(0.2)) From c4a854d4c15d4e123c85cad3b7e1c4e46c7153f8 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 22:29:29 +0800 Subject: [PATCH 09/21] Get active application before debounce delay --- .../FeatureReducers/WidgetFeature.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift index 7878379a..5409ed95 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/WidgetFeature.swift @@ -549,20 +549,22 @@ public struct WidgetFeature: ReducerProtocol { } } + #warning("TODO: control windows in their dedicated reducers.") case let .updateWindowOpacity(immediately): let isChatPanelDetached = state.chatPanelState.chatPanelInASeparateWindow let hasChat = !state.chatPanelState.chatTabGroup.tabInfo.isEmpty let shouldDebounce = !immediately && Date().timeIntervalSince(state.lastUpdateWindowOpacityTime) < 1 return .run { send in + let activeApp = xcodeInspector.activeApplication if shouldDebounce { try await mainQueue.sleep(for: .seconds(0.2)) } try Task.checkCancellation() let task = Task { @MainActor in - if let app = xcodeInspector.activeApplication, app.isXcode { + if let activeApp, activeApp.isXcode { let application = AXUIElementCreateApplication( - app.runningApplication.processIdentifier + activeApp.runningApplication.processIdentifier ) /// We need this to hide the windows when Xcode is minimized. let noFocus = application.focusedWindow == nil @@ -576,10 +578,7 @@ public struct WidgetFeature: ReducerProtocol { } else { windows.chatPanelWindow.alphaValue = noFocus ? 0 : 1 } - } else if - let app = xcodeInspector.activeApplication, - app.runningApplication.bundleIdentifier == Bundle.main.bundleIdentifier - { + } else if let activeApp, activeApp.isExtensionService { let noFocus = { guard let xcode = xcodeInspector.latestActiveXcode else { return true } From 7d26088cf00b598e395346affa6012bb790d5074 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 22:50:01 +0800 Subject: [PATCH 10/21] Add a fixme that I have no idea how to fix at the moment --- Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift b/Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift index 5323ffb1..b46c705c 100644 --- a/Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift +++ b/Core/Sources/SuggestionWidget/WidgetPositionStrategy.swift @@ -312,7 +312,6 @@ enum UpdateLocationStrategy { let caseConsiderCompletionPanel = { (completionPanelRect: CGRect) -> WidgetLocation.PanelLocation? in let completionPanelBelowCursor = completionPanelRect.minY >= selectionFrame.midY - switch (completionPanelBelowCursor, alignPanelTopToAnchor) { case (true, false), (false, true): // case: different position, place the suggestion as it should be @@ -394,6 +393,9 @@ enum UpdateLocationStrategy { var firstLineRange: CFRange = .init() let foundFirstLine = AXValueGetValue(selectedRange, .cfRange, &firstLineRange) firstLineRange.length = 0 + + #warning("FIXME: When selection is too low and out of the screen, the selection range becomes something else.") + if foundFirstLine, let firstLineSelectionRange = AXValueCreate(.cfRange, &firstLineRange), let firstLineRect: AXValue = try? editor.copyParameterizedValue( @@ -411,4 +413,3 @@ enum UpdateLocationStrategy { return selectionFrame } } - From 9cea9c3b89bfc3f031b7d4dd4e4dbeff9ffc3ecc Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Sun, 10 Dec 2023 23:34:06 +0800 Subject: [PATCH 11/21] Update --- Pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pro b/Pro index 9b000a35..a47e180b 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit 9b000a357b3e5679ee004fd1cc9937781f217926 +Subproject commit a47e180b29fa6f10546ec35f54c3e653aa3ae700 From bcab2070190c5aaba0209c486af4dd6b2842cc5d Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Mon, 11 Dec 2023 00:00:24 +0800 Subject: [PATCH 12/21] Fix that prompt to code may not be opened by keybindings But we really need some refactor about the widgets --- Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift | 1 - .../SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift b/Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift index 4fd43f94..5af9f2ba 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/PanelFeature.swift @@ -111,7 +111,6 @@ public struct PanelFeature: ReducerProtocol { case .removeDisplayedContent: state.content.error = nil - state.content.promptToCodeGroup.activeDocumentURL = nil state.content.suggestion = nil return .none diff --git a/Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift b/Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift index 14d49b56..d689509a 100644 --- a/Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift +++ b/Core/Sources/SuggestionWidget/FeatureReducers/PromptToCodeGroup.swift @@ -19,7 +19,6 @@ public struct PromptToCodeGroup: ReducerProtocol { return promptToCodes[id: id] } set { - activeDocumentURL = newValue?.id if let id = newValue?.id { promptToCodes[id: id] = newValue } From a4bf72da916c259049bcf448b47d6dab200685f0 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Mon, 11 Dec 2023 15:46:29 +0800 Subject: [PATCH 13/21] Pretty print GitHub Copilot logs --- Pro | 2 +- .../CopilotLocalProcessServer.swift | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Pro b/Pro index a47e180b..2100084b 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit a47e180b29fa6f10546ec35f54c3e653aa3ae700 +Subproject commit 2100084bfd27e5b1467119f714731c2f17009c0f diff --git a/Tool/Sources/GitHubCopilotService/CopilotLocalProcessServer.swift b/Tool/Sources/GitHubCopilotService/CopilotLocalProcessServer.swift index 2b1a0882..e655a91d 100644 --- a/Tool/Sources/GitHubCopilotService/CopilotLocalProcessServer.swift +++ b/Tool/Sources/GitHubCopilotService/CopilotLocalProcessServer.swift @@ -108,13 +108,13 @@ extension CopilotLocalProcessServer: LanguageServerProtocol.Server { server.sendNotification(notif, completionHandler: completionHandler) } - + /// Cancel ongoing completion requests. public func cancelOngoingTasks() async { guard let server = wrappedServer, process.isRunning else { return } - + let task = Task { @MainActor in for id in self.ongoingCompletionRequestIDs { switch id { @@ -126,7 +126,7 @@ extension CopilotLocalProcessServer: LanguageServerProtocol.Server { } self.ongoingCompletionRequestIDs = [] } - + await task.value } @@ -200,32 +200,44 @@ extension CustomJSONRPCLanguageServer { block: @escaping (Error?) -> Void ) -> Bool { let methodName = anyNotification.method + let debugDescription = { + if let params = anyNotification.params { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + if let jsonData = try? encoder.encode(params), + let text = String(data: jsonData, encoding: .utf8) + { + return text + } + } + return "N/A" + }() switch methodName { case "window/logMessage": if UserDefaults.shared.value(for: \.gitHubCopilotVerboseLog) { Logger.gitHubCopilot - .info("\(anyNotification.method): \(anyNotification.params.debugDescription)") + .info("\(anyNotification.method): \(debugDescription)") } block(nil) return true case "LogMessage": if UserDefaults.shared.value(for: \.gitHubCopilotVerboseLog) { Logger.gitHubCopilot - .info("\(anyNotification.method): \(anyNotification.params.debugDescription)") + .info("\(anyNotification.method): \(debugDescription)") } - block(nil) + block(nil)// return true case "statusNotification": if UserDefaults.shared.value(for: \.gitHubCopilotVerboseLog) { Logger.gitHubCopilot - .info("\(anyNotification.method): \(anyNotification.params.debugDescription)") + .info("\(anyNotification.method): \(debugDescription)") } block(nil) return true case "featureFlagsNotification": if UserDefaults.shared.value(for: \.gitHubCopilotVerboseLog) { Logger.gitHubCopilot - .info("\(anyNotification.method): \(anyNotification.params.debugDescription)") + .info("\(anyNotification.method): \(debugDescription)") } block(nil) return true From dfc6c183cc4d62feb59963e697db8eb5d13ba692 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 02:32:41 +0800 Subject: [PATCH 14/21] Display reference kind in chat --- Core/Sources/ChatGPTChatTab/Chat.swift | 17 ++- Core/Sources/ChatGPTChatTab/ChatPanel.swift | 3 +- .../ChatGPTChatTab/Views/BotMessage.swift | 107 ++++++++++++++++-- Pro | 2 +- Tool/Sources/OpenAIService/Models.swift | 27 ++++- 5 files changed, 140 insertions(+), 16 deletions(-) diff --git a/Core/Sources/ChatGPTChatTab/Chat.swift b/Core/Sources/ChatGPTChatTab/Chat.swift index d5e32804..ee9b9dd0 100644 --- a/Core/Sources/ChatGPTChatTab/Chat.swift +++ b/Core/Sources/ChatGPTChatTab/Chat.swift @@ -14,16 +14,26 @@ public struct DisplayedChatMessage: Equatable { } public struct Reference: Equatable { + public typealias Kind = ChatMessage.Reference.Kind + public var title: String public var subtitle: String public var uri: String public var startLine: Int? - - public init(title: String, subtitle: String, uri: String, startLine: Int?) { + public var kind: Kind + + public init( + title: String, + subtitle: String, + uri: String, + startLine: Int?, + kind: Kind + ) { self.title = title self.subtitle = subtitle self.uri = uri self.startLine = startLine + self.kind = kind } } @@ -304,7 +314,8 @@ struct Chat: ReducerProtocol { title: $0.title, subtitle: $0.subTitle, uri: $0.uri, - startLine: $0.startLine + startLine: $0.startLine, + kind: $0.kind ) } ) diff --git a/Core/Sources/ChatGPTChatTab/ChatPanel.swift b/Core/Sources/ChatGPTChatTab/ChatPanel.swift index adaa78b0..7a729bfd 100644 --- a/Core/Sources/ChatGPTChatTab/ChatPanel.swift +++ b/Core/Sources/ChatGPTChatTab/ChatPanel.swift @@ -440,7 +440,8 @@ struct ChatPanel_Preview: PreviewProvider { title: "Hello Hello Hello Hello", subtitle: "Hi Hi Hi Hi", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .class ), ] ), diff --git a/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift b/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift index 1bf4c316..069d9b02 100644 --- a/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift +++ b/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift @@ -108,17 +108,20 @@ struct ReferenceList: View { chat.send(.referenceClicked(reference)) }) { HStack(spacing: 8) { + ReferenceIcon(kind: reference.kind) + .layoutPriority(2) Text(reference.title) .truncationMode(.middle) .lineLimit(1) + .layoutPriority(1) Text(reference.subtitle) .lineLimit(1) .truncationMode(.middle) - .layoutPriority(/*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/) .foregroundStyle(.tertiary) + .layoutPriority(/*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/) } - .padding(.vertical, 6) - .padding(.horizontal, 8) + .padding(.vertical, 4) + .padding(.horizontal, 4) .frame(maxWidth: .infinity, alignment: .leading) .overlay { RoundedRectangle(cornerRadius: 4) @@ -134,6 +137,83 @@ struct ReferenceList: View { } } +struct ReferenceIcon: View { + let kind: DisplayedChatMessage.Reference.Kind + + var body: some View { + RoundedRectangle(cornerRadius: 4) + .fill({ + switch kind { + case .class: + Color.purple + case .struct: + Color.purple + case .enum: + Color.purple + case .actor: + Color.purple + case .protocol: + Color.purple + case .extension: + Color.indigo + case .case: + Color.green + case .property: + Color.teal + case .typealias: + Color.orange + case .function: + Color.teal + case .method: + Color.blue + case .text: + Color.gray + case .webpage: + Color.blue + case .other: + Color.gray + } + }()) + .frame(width: 22, height: 22) + .overlay(alignment: .center) { + Group { + switch kind { + case .class: + Text("C") + case .struct: + Text("S") + case .enum: + Text("E") + case .actor: + Text("A") + case .protocol: + Text("Pr") + case .extension: + Text("Ex") + case .case: + Text("K") + case .property: + Text("P") + case .typealias: + Text("T") + case .function: + Text("𝑓") + case .method: + Text("M") + case .text: + Text("Tx") + case .webpage: + Text("Wb") + case .other: + Text("Ot") + } + } + .font(.system(size: 12).monospaced()) + .foregroundColor(.white) + } + } +} + #Preview("Bot Message") { BotMessage( id: "1", @@ -147,7 +227,8 @@ struct ReferenceList: View { title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .class ), count: 20), chat: .init(initialState: .init(), reducer: Chat(service: .init())) ) @@ -161,37 +242,43 @@ struct ReferenceList: View { title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .class ), .init( title: "BotMessage.swift:100-102", subtitle: "/Core/Sources/ChatGPTChatTab/Views", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .struct ), .init( title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .function ), .init( title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .case ), .init( title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .extension ), .init( title: "ReferenceList", subtitle: "/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift:100", uri: "https://google.com", - startLine: nil + startLine: nil, + kind: .webpage ), ], chat: .init(initialState: .init(), reducer: Chat(service: .init()))) } diff --git a/Pro b/Pro index 2100084b..fd1b2fef 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit 2100084bfd27e5b1467119f714731c2f17009c0f +Subproject commit fd1b2fefd39c53f403c58da344f69d2494d795e7 diff --git a/Tool/Sources/OpenAIService/Models.swift b/Tool/Sources/OpenAIService/Models.swift index ea19c52f..8ff25b96 100644 --- a/Tool/Sources/OpenAIService/Models.swift +++ b/Tool/Sources/OpenAIService/Models.swift @@ -28,12 +28,31 @@ public struct ChatMessage: Equatable, Codable { } public struct Reference: Codable, Equatable { + public enum Kind: String, Codable { + case `class` + case `struct` + case `enum` + case `actor` + case `protocol` + case `extension` + case `case` + case property + case `typealias` + case function + case method + case text + case webpage + case other + } + public var title: String public var subTitle: String public var uri: String public var content: String public var startLine: Int? public var endLine: Int? + @FallbackDecoding + public var kind: Kind public init( title: String, @@ -41,7 +60,8 @@ public struct ChatMessage: Equatable, Codable { content: String, uri: String, startLine: Int?, - endLine: Int? + endLine: Int?, + kind: Kind ) { self.title = title self.subTitle = subTitle @@ -49,6 +69,7 @@ public struct ChatMessage: Equatable, Codable { self.uri = uri self.startLine = startLine self.endLine = endLine + self.kind = kind } } @@ -112,3 +133,7 @@ public struct ChatMessage: Equatable, Codable { } } +public struct ReferenceKindFallback: FallbackValueProvider { + public static var defaultValue: ChatMessage.Reference.Kind { .other } +} + From 558498a6e83bbe6bfa3e4336150e150f4430cb75 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 14:37:30 +0800 Subject: [PATCH 15/21] Fix --- Core/Sources/ChatGPTChatTab/Views/BotMessage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift b/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift index 069d9b02..c8efac88 100644 --- a/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift +++ b/Core/Sources/ChatGPTChatTab/Views/BotMessage.swift @@ -118,7 +118,7 @@ struct ReferenceList: View { .lineLimit(1) .truncationMode(.middle) .foregroundStyle(.tertiary) - .layoutPriority(/*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/) + .layoutPriority(0) } .padding(.vertical, 4) .padding(.horizontal, 4) From ec42770d74af56669b7e5c75d6eea346f515b3f0 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 14:37:54 +0800 Subject: [PATCH 16/21] Add feature flag disableFileContentManipulationByCheatsheet --- Core/Sources/HostApp/DebugView.swift | 15 ++++++++++----- Tool/Sources/Preferences/Keys.swift | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Core/Sources/HostApp/DebugView.swift b/Core/Sources/HostApp/DebugView.swift index 5d93f5e5..e4054dd1 100644 --- a/Core/Sources/HostApp/DebugView.swift +++ b/Core/Sources/HostApp/DebugView.swift @@ -17,6 +17,8 @@ final class DebugSettings: ObservableObject { @AppStorage(\.useUserDefaultsBaseAPIKeychain) var useUserDefaultsBaseAPIKeychain @AppStorage(\.disableEnhancedWorkspace) var disableEnhancedWorkspace @AppStorage(\.disableGitIgnoreCheck) var disableGitIgnoreCheck + @AppStorage(\.disableFileContentManipulationByCheatsheet) + var disableFileContentManipulationByCheatsheet init() {} } @@ -60,19 +62,23 @@ struct DebugSettingsView: View { Toggle(isOn: $settings.useUserDefaultsBaseAPIKeychain) { Text("Store API keys in UserDefaults") } - + Toggle(isOn: $settings.disableEnhancedWorkspace) { Text("Disable enhanced workspace") } - + Toggle(isOn: $settings.disableGitIgnoreCheck) { Text("Disable git ignore check") } - + + Toggle(isOn: $settings.disableFileContentManipulationByCheatsheet) { + Text("Disable file content manipulation by cheatsheet") + } + Button("Reset migration version to 0") { UserDefaults.shared.set(nil, forKey: "OldMigrationVersion") } - + Button("Reset 0.23.0 migration") { UserDefaults.shared.set("239", forKey: "OldMigrationVersion") UserDefaults.shared.set(nil, forKey: "MigrateTo240Finished") @@ -92,4 +98,3 @@ struct DebugSettingsView_Preview: PreviewProvider { } } - diff --git a/Tool/Sources/Preferences/Keys.swift b/Tool/Sources/Preferences/Keys.swift index cae7c756..d1808434 100644 --- a/Tool/Sources/Preferences/Keys.swift +++ b/Tool/Sources/Preferences/Keys.swift @@ -550,6 +550,10 @@ public extension UserDefaultPreferenceKeys { var disableGitIgnoreCheck: FeatureFlag { .init(defaultValue: false, key: "FeatureFlag-DisableGitIgnoreCheck") } + + var disableFileContentManipulationByCheatsheet: FeatureFlag { + .init(defaultValue: true, key: "FeatureFlag-DisableFileContentManipulationByCheatsheet") + } var disableEnhancedWorkspace: FeatureFlag { .init( From 3416dc817e6e637dc224a756c49ce301c4579b46 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 14:38:00 +0800 Subject: [PATCH 17/21] Update --- Pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pro b/Pro index fd1b2fef..a9e9bebd 160000 --- a/Pro +++ b/Pro @@ -1 +1 @@ -Subproject commit fd1b2fefd39c53f403c58da344f69d2494d795e7 +Subproject commit a9e9bebd3af1af4fe5b20d2c3df4e436938c2bb6 From 43929f476d18c45663220c376da87e03ac834d4a Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 15:13:41 +0800 Subject: [PATCH 18/21] Bump version to 0.28.3 --- Version.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Version.xcconfig b/Version.xcconfig index fc9e5829..e5fad3e5 100644 --- a/Version.xcconfig +++ b/Version.xcconfig @@ -1,3 +1,3 @@ -APP_VERSION = 0.28.2 -APP_BUILD = 292 +APP_VERSION = 0.28.3 +APP_BUILD = 293 From 2b3bb3a8d8dafd1b2a5c1e496593410acea9bd62 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 15:13:51 +0800 Subject: [PATCH 19/21] Update appcast.xml --- appcast.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/appcast.xml b/appcast.xml index b2b9b2f7..18d180d6 100644 --- a/appcast.xml +++ b/appcast.xml @@ -3,6 +3,18 @@ Copilot for Xcode + + 0.28.3 + Tue, 12 Dec 2023 14:29:58 +0800 + 293 + 0.28.3 + 12.0 + + https://github.com/intitni/CopilotForXcode/releases/tag/0.28.3 + + + + 0.28.2 Mon, 04 Dec 2023 23:11:26 +0800 From 447cb12a550e86e32e6885600845c48388c4dfcd Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 15:15:10 +0800 Subject: [PATCH 20/21] Update --- .github/workflows/close_inactive_issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/close_inactive_issues.yml b/.github/workflows/close_inactive_issues.yml index 6be38831..1fae3c1f 100644 --- a/.github/workflows/close_inactive_issues.yml +++ b/.github/workflows/close_inactive_issues.yml @@ -15,7 +15,7 @@ jobs: days-before-issue-stale: 30 days-before-issue-close: 14 stale-issue-label: "stale" - exempt-issue-labels: "low priority, help wanted, planned" + exempt-issue-labels: "low priority, help wanted, planned, investigating, blocked" stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." days-before-pr-stale: -1 From 948449daa789f6f7b25ccabd4ab8ff34d39cc9d6 Mon Sep 17 00:00:00 2001 From: Shx Guo Date: Tue, 12 Dec 2023 15:38:01 +0800 Subject: [PATCH 21/21] Update ChatSettingsView.swift --- .../FeatureSettings/ChatSettingsView.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Core/Sources/HostApp/FeatureSettings/ChatSettingsView.swift b/Core/Sources/HostApp/FeatureSettings/ChatSettingsView.swift index b33770d0..e2a70d16 100644 --- a/Core/Sources/HostApp/FeatureSettings/ChatSettingsView.swift +++ b/Core/Sources/HostApp/FeatureSettings/ChatSettingsView.swift @@ -404,13 +404,14 @@ struct ChatSettingsView: View { } // MARK: - Preview - -#Preview { - ScrollView { - ChatSettingsView() - .padding() - } - .frame(height: 800) - .environment(\.overrideFeatureFlag, \.never) -} +// +//#Preview { +// ScrollView { +// ChatSettingsView() +// .padding() +// } +// .frame(height: 800) +// .environment(\.overrideFeatureFlag, \.never) +//} +//