diff --git a/platform/core-api/src/com/intellij/openapi/progress/BridgeJobIndicatorBase.kt b/platform/core-api/src/com/intellij/openapi/progress/BridgeJobIndicatorBase.kt new file mode 100644 index 000000000000..1f6be72e8f73 --- /dev/null +++ b/platform/core-api/src/com/intellij/openapi/progress/BridgeJobIndicatorBase.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.progress + +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.util.registry.Registry +import org.jetbrains.annotations.ApiStatus + +/** + * A base class for progress indicators that depend on an instance of a job. + * + * The motivation is the following: + * ```kotlin + * coroutineToIndicator { indicator -> + * runProcess({}, indicator) + * } + * ``` + * Here, [ProgressManager.runProcess] calls [ProgressIndicator.start] on `indicator`, + * which would lead to removal of cancellations status for [EmptyProgressIndicator]. To avoid it, we reimplement [EmptyProgressIndicatorBase] directly + */ +@ApiStatus.Internal +@Suppress("UsagesOfObsoleteApi") +internal abstract class BridgeJobIndicatorBase(modalityState: ModalityState): EmptyProgressIndicatorBase(modalityState), StandardProgressIndicator { + companion object { + private val PLACEHOLDER: Throwable = Throwable( + "Dummy throwable that indicates cancellation of EmptyProgressIndicator.\nSet `ide.rich.cancellation.traces` to `true` to get real origin of cancellation.") + } + + @Volatile + private var cancellationCause: Throwable? = null + + override fun cancel() { + cancellationCause = if (Registry.`is`("ide.rich.cancellation.traces", false)) { + Throwable("Origin of cancellation of $this") + } + else { + PLACEHOLDER + } + } + + override fun isCanceled(): Boolean { + return cancellationCause != null + } + + override fun getCancellationCause(): Throwable? { + return cancellationCause.takeIf { it != PLACEHOLDER } + } +} diff --git a/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java index 8015b3d0934e..2e4a4e7f7a72 100644 --- a/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java +++ b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java @@ -3,6 +3,7 @@ package com.intellij.openapi.progress; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.util.registry.Registry; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus.Obsolete; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -42,8 +43,14 @@ public class EmptyProgressIndicator extends EmptyProgressIndicatorBase implement ProgressManager.canceled(this); } - final @Nullable Throwable getCancellationCause() { - return myCancellationRequester; + @ApiStatus.Internal + @Override + protected final @Nullable Throwable getCancellationCause() { + if (myCancellationRequester != PLACEHOLDER) { + return myCancellationRequester; + } else { + return null; + } } @Override diff --git a/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicatorBase.java b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicatorBase.java index 71de3d43c2a6..6772a78bf397 100644 --- a/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicatorBase.java +++ b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicatorBase.java @@ -3,8 +3,10 @@ package com.intellij.openapi.progress; import com.intellij.diagnostic.PluginException; import com.intellij.openapi.application.ModalityState; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus.Obsolete; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Objects; @@ -72,14 +74,19 @@ public abstract class EmptyProgressIndicatorBase implements ProgressIndicator { public final void checkCanceled() { if (isCanceled() && myNonCancelableSectionCount == 0 && !Cancellation.isInNonCancelableSection()) { ProcessCanceledException e = new ProcessCanceledException(); - if (this instanceof EmptyProgressIndicator) { - Throwable cause = Objects.requireNonNull(((EmptyProgressIndicator)this).getCancellationCause()); - e.addSuppressed(cause); + Throwable cancellationCause = getCancellationCause(); + if (cancellationCause != null) { + e.addSuppressed(cancellationCause); } throw e; } } + @ApiStatus.Internal + protected @Nullable Throwable getCancellationCause() { + return null; + } + @Override public void setText(String text) { } diff --git a/platform/core-api/src/com/intellij/openapi/progress/RawProgressReporterIndicator.kt b/platform/core-api/src/com/intellij/openapi/progress/RawProgressReporterIndicator.kt index 372c21ac1c2b..3fb7eff782bd 100644 --- a/platform/core-api/src/com/intellij/openapi/progress/RawProgressReporterIndicator.kt +++ b/platform/core-api/src/com/intellij/openapi/progress/RawProgressReporterIndicator.kt @@ -7,8 +7,7 @@ import com.intellij.platform.util.progress.RawProgressReporter internal class RawProgressReporterIndicator( private val reporter: RawProgressReporter, contextModality: ModalityState, -) : EmptyProgressIndicator(contextModality) { - +) : BridgeJobIndicatorBase(contextModality) { override fun setText(text: String?) { reporter.text(text) } diff --git a/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt b/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt index 1770e05a9896..91f5c4d6fe82 100644 --- a/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt +++ b/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt @@ -558,7 +558,7 @@ private fun contextToIndicator(ctx: CoroutineContext, action: () -> T): T { * We keep this class as an inheritor of [EmptyProgressIndicator] for ease of debugging -- if one sees an instance of this class, * it means that the currently used indicator depends on some job. */ -private class JobDependentIndicator(modalityState: ModalityState): EmptyProgressIndicator(modalityState) +private class JobDependentIndicator(modalityState: ModalityState): BridgeJobIndicatorBase(modalityState) @Throws(CancellationException::class) @Internal