diff --git a/Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift b/Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift index 904172b8..4e09de2b 100644 --- a/Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift +++ b/Core/Sources/Service/SuggestionPresenter/PresentInWindowSuggestionPresenter.swift @@ -28,6 +28,7 @@ struct PresentInWindowSuggestionPresenter { func presentError(_ error: Error) { if error is CancellationError { return } + if let urlError = error as? URLError, urlError.code == URLError.cancelled { return } Task { @MainActor in let controller = GraphicalUserInterfaceController.shared.suggestionWidget controller.presentError(error.localizedDescription) diff --git a/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift b/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift index 74fe11c0..88087ec9 100644 --- a/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift +++ b/Core/Sources/SuggestionWidget/SuggestionWidgetController.swift @@ -275,8 +275,9 @@ public final class SuggestionWidgetController: NSObject { public extension SuggestionWidgetController { func suggestCode(fileURL: URL) { - widgetViewModel.isProcessing = false Task { + markAsProcessing(true) + defer { markAsProcessing(false) } if let suggestion = await dataSource?.suggestionForFile(at: fileURL) { suggestionPanelViewModel.content = .suggestion(suggestion) suggestionPanelViewModel.isPanelDisplayed = true @@ -285,25 +286,28 @@ public extension SuggestionWidgetController { } func discardSuggestion(fileURL: URL) { - widgetViewModel.isProcessing = false Task { await updateContentForActiveEditor(fileURL: fileURL) } } func markAsProcessing(_ isProcessing: Bool) { - widgetViewModel.isProcessing = isProcessing + if isProcessing { + widgetViewModel.markIsProcessing() + } else { + widgetViewModel.endIsProcessing() + } } func presentError(_ errorDescription: String) { suggestionPanelViewModel.content = .error(errorDescription) suggestionPanelViewModel.isPanelDisplayed = true - widgetViewModel.isProcessing = false } func presentChatRoom(fileURL: URL) { - widgetViewModel.isProcessing = false Task { + markAsProcessing(true) + defer { markAsProcessing(false) } if let chat = await dataSource?.chatForFile(at: fileURL) { chatWindowViewModel.chat = chat chatWindowViewModel.isPanelDisplayed = true @@ -343,15 +347,15 @@ public extension SuggestionWidgetController { } func closeChatRoom(fileURL: URL) { - widgetViewModel.isProcessing = false Task { await updateContentForActiveEditor(fileURL: fileURL) } } func presentPromptToCode(fileURL: URL) { - widgetViewModel.isProcessing = false Task { + markAsProcessing(true) + defer { markAsProcessing(false) } if let provider = await dataSource?.promptToCodeForFile(at: fileURL) { suggestionPanelViewModel.content = .promptToCode(provider) suggestionPanelViewModel.isPanelDisplayed = true @@ -367,7 +371,6 @@ public extension SuggestionWidgetController { } func discardPromptToCode(fileURL: URL) { - widgetViewModel.isProcessing = false Task { await updateContentForActiveEditor(fileURL: fileURL) } diff --git a/Core/Sources/SuggestionWidget/WidgetView.swift b/Core/Sources/SuggestionWidget/WidgetView.swift index 50bfae00..305214b0 100644 --- a/Core/Sources/SuggestionWidget/WidgetView.swift +++ b/Core/Sources/SuggestionWidget/WidgetView.swift @@ -6,9 +6,40 @@ import SwiftUI @MainActor final class WidgetViewModel: ObservableObject { - @Published var isProcessing: Bool + struct IsProcessingCounter { + var expirationDate: TimeInterval + } + + private var isProcessingCounters = [IsProcessingCounter]() + private var cleanupIsProcessingCounterTask: Task? + @Published private(set) var isProcessing: Bool @Published var currentFileURL: URL? + func markIsProcessing(date: Date = Date()) { + let deadline = date.timeIntervalSince1970 + 20 + isProcessingCounters.append(IsProcessingCounter(expirationDate: deadline)) + isProcessing = true + + cleanupIsProcessingCounterTask?.cancel() + cleanupIsProcessingCounterTask = Task { [weak self] in + try await Task.sleep(nanoseconds: 20 * 1_000_000_000) + try Task.checkCancellation() + Task { @MainActor [weak self] in + guard let self else { return } + isProcessingCounters.removeAll() + isProcessing = false + } + } + } + + func endIsProcessing(date: Date = Date()) { + if !isProcessingCounters.isEmpty { + isProcessingCounters.removeFirst() + } + isProcessingCounters.removeAll(where: { $0.expirationDate < date.timeIntervalSince1970 }) + isProcessing = !isProcessingCounters.isEmpty + } + init(isProcessing: Bool = false) { self.isProcessing = isProcessing } diff --git a/Version.xcconfig b/Version.xcconfig index ce111d9e..f45a2716 100644 --- a/Version.xcconfig +++ b/Version.xcconfig @@ -1,2 +1,2 @@ -APP_VERSION = 0.17.0 -APP_BUILD = 170 +APP_VERSION = 0.17.1 +APP_BUILD = 171 diff --git a/appcast.xml b/appcast.xml index 1f42d684..41bb3495 100644 --- a/appcast.xml +++ b/appcast.xml @@ -3,6 +3,18 @@ Copilot for Xcode + + 0.17.1 + Wed, 31 May 2023 12:30:21 +0800 + 171 + 0.17.1 + 12.0 + + https://github.com/intitni/CopilotForXcode/releases/tag/0.17.1 + + + + 0.17.0 Sat, 27 May 2023 15:33:25 +0800