From c627d8d26dd0aebfa587d7d3fe37f25979a34518 Mon Sep 17 00:00:00 2001 From: "Artem.Bukhonov" Date: Fri, 25 Oct 2024 19:48:33 +0200 Subject: [PATCH] LLM-12328 Subscribe listeners with a plugin service scope disposable Add util methods for creating or binding a disposable to a CoroutineScope. (cherry picked from commit f03d5ae7601941ca59ddf483210d2426aabb4944) GitOrigin-RevId: d39517066a3cd63c25b18c119f605e14b1b58d16 --- .../openapi/editor/ex/util/EditorUtil.java | 1 - .../src/com/intellij/util/coroutineScope.kt | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java index ba412d7870f3..cac87069dce2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java @@ -972,7 +972,6 @@ public final class EditorUtil { } public static void disposeWithEditor(@NotNull Editor editor, @NotNull Disposable disposable) { - ThreadingAssertions.assertEventDispatchThread(); ReadAction.run(() -> { if (editor.isDisposed()) { Disposer.dispose(disposable); diff --git a/platform/util/src/com/intellij/util/coroutineScope.kt b/platform/util/src/com/intellij/util/coroutineScope.kt index 61fa752bab01..f862c065494d 100644 --- a/platform/util/src/com/intellij/util/coroutineScope.kt +++ b/platform/util/src/com/intellij/util/coroutineScope.kt @@ -3,11 +3,15 @@ package com.intellij.util import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.cancel import kotlinx.coroutines.job +import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus.Experimental import org.jetbrains.annotations.ApiStatus.Internal +import java.util.concurrent.atomic.AtomicReference @Internal @Experimental @@ -21,3 +25,37 @@ fun Job.cancelOnDispose(disposable: Disposable, disposeOnCompletion: Boolean = t } } } + +/** + * Attaches [this] disposable to a scope [cs] so that when [cs] is canceled, [this] is also disposed. + * + * When [this] disposable is disposed from another place [cs] won't be touched. + */ +fun Disposable.attachAsChildTo(cs: CoroutineScope) { + val disposableRef = AtomicReference(this) + val job = cs.launch { + try { + awaitCancellation() + } + finally { + disposableRef.getAndSet(null)?.let { + Disposer.dispose(it) + } + } + } + val registered = Disposer.tryRegister(this) { + disposableRef.getAndSet(null)?.let { + job.cancel("disposed") + } + } + if (!registered) job.cancel("disposable is already disposed") +} + +/** + * Creates a disposable that will be disposed on [this] scope cancellation. + * + * Manual disposal of a created Disposable is also possible and doesn't touch [this] scope. + */ +fun CoroutineScope.asDisposable(): Disposable { + return Disposer.newDisposable("Disposable from scope: $this").also { it.attachAsChildTo(this) } +} \ No newline at end of file