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
This commit is contained in:
Artem.Bukhonov
2024-11-18 13:31:20 +00:00
committed by intellij-monorepo-bot
parent 5945a6d2c6
commit c627d8d26d
2 changed files with 38 additions and 1 deletions
@@ -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);
@@ -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<Disposable?>(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) }
}