diff --git a/java/debugger/impl/resources/META-INF/java-debugger.xml b/java/debugger/impl/resources/META-INF/java-debugger.xml
index 98483367c378..5a46251d3f6b 100644
--- a/java/debugger/impl/resources/META-INF/java-debugger.xml
+++ b/java/debugger/impl/resources/META-INF/java-debugger.xml
@@ -95,7 +95,7 @@
-
+
diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/HotSwapDebugSessionListener.kt b/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/HotSwapDebugSessionListener.kt
deleted file mode 100644
index f62c0ae9d1b1..000000000000
--- a/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/HotSwapDebugSessionListener.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
-package com.intellij.debugger.impl.hotswap
-
-import com.intellij.debugger.engine.JavaDebugProcess
-import com.intellij.debugger.impl.DebuggerSession
-import com.intellij.debugger.ui.HotSwapStatusListener
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.components.Service
-import com.intellij.openapi.components.service
-import com.intellij.openapi.components.serviceAsync
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.startup.ProjectActivity
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.registry.Registry
-import com.intellij.util.asDisposable
-import com.intellij.xdebugger.XDebugProcess
-import com.intellij.xdebugger.XDebugSession
-import com.intellij.xdebugger.XDebuggerManager
-import com.intellij.xdebugger.XDebuggerManagerListener
-import com.intellij.xdebugger.impl.XDebugSessionImpl
-import com.intellij.xdebugger.impl.hotswap.HotSwapResultListener
-import com.intellij.xdebugger.impl.hotswap.HotSwapSession
-import com.intellij.xdebugger.impl.hotswap.HotSwapSessionManager
-import kotlinx.coroutines.CoroutineScope
-import java.util.concurrent.ConcurrentHashMap
-
-private data class JavaHotSwapSessionEntry(val hotSwapSession: HotSwapSession<*>, val disposable: Disposable)
-
-private class HotSwapManagerInitActivity : ProjectActivity {
- override suspend fun execute(project: Project) {
- project.serviceAsync()
- }
-}
-
-@Service(Service.Level.PROJECT)
-internal class HotSwapDebugSessionManager(project: Project, cs: CoroutineScope) : XDebuggerManagerListener {
- private val sessions = ConcurrentHashMap()
-
- init {
- project.getMessageBus().connect(cs).subscribe(XDebuggerManager.TOPIC, this)
- }
-
- fun createSessionListenerOrNull(session: DebuggerSession): HotSwapStatusListener? {
- val hotSwapSession = findHotSwapSession(session) ?: return null
- return HotSwapStatusListenerAdapter(session, hotSwapSession.startHotSwapListening())
- }
-
- internal fun findHotSwapSession(session: DebuggerSession): HotSwapSession<*>? = sessions[session]?.hotSwapSession
-
- override fun processStarted(debugProcess: XDebugProcess) {
- val session = (debugProcess as? JavaDebugProcess)?.debuggerSession ?: return
- if (!Registry.`is`("debugger.hotswap.floating.toolbar")) return
- val disposable = (debugProcess.session as XDebugSessionImpl).coroutineScope.asDisposable()
- val hotSwapSession = HotSwapSessionManager.getInstance(session.project).createSession(JvmHotSwapProvider(session), disposable)
- sessions[session] = JavaHotSwapSessionEntry(hotSwapSession, disposable)
- }
-
- override fun processStopped(debugProcess: XDebugProcess) {
- val javaDebugSession = (debugProcess as? JavaDebugProcess)?.debuggerSession ?: return
- val sessionEntry = sessions.remove(javaDebugSession) ?: return
- Disposer.dispose(sessionEntry.disposable)
- }
-
- override fun currentSessionChanged(previousSession: XDebugSession?, currentSession: XDebugSession?) {
- if (currentSession == null) return
- val (_, entry) = sessions.entries.firstOrNull { (session, _) -> session.xDebugSession === currentSession } ?: return
- HotSwapSessionManager.getInstance(currentSession.project).onSessionSelected(entry.hotSwapSession)
- }
-
- companion object {
- @JvmStatic
- fun getInstance(project: Project): HotSwapDebugSessionManager = project.service()
- }
-}
-
-private class HotSwapStatusListenerAdapter(private val originalSession: DebuggerSession, private val listener: HotSwapResultListener) : HotSwapStatusListener {
- override fun onSuccess(sessions: MutableList?) {
- sessions?.createListeners(originalSession)?.forEach { it.onSuccessfulReload() }
- listener.onSuccessfulReload()
- }
-
- override fun onFailure(sessions: MutableList?) {
- sessions?.createListeners(originalSession)?.forEach { it.onFailure() }
- listener.onFailure()
- }
-
- override fun onCancel(sessions: MutableList?) {
- sessions?.createListeners(originalSession)?.forEach { it.onCanceled() }
- listener.onCanceled()
- }
-
- override fun onNothingToReload(sessions: MutableList?) {
- sessions?.createListeners(originalSession)?.forEach { it.onFinish() }
- listener.onFinish()
- }
-
- private fun List.createListeners(except: DebuggerSession) = filter { it !== except }
- .mapNotNull { HotSwapDebugSessionManager.getInstance(except.project).findHotSwapSession(it) }
- .map { it.startHotSwapListening() }
-}
diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/JvmHotSwapInDebugSessionEnabler.kt b/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/JvmHotSwapInDebugSessionEnabler.kt
new file mode 100644
index 000000000000..bbe920c1fdb6
--- /dev/null
+++ b/java/debugger/impl/src/com/intellij/debugger/impl/hotswap/JvmHotSwapInDebugSessionEnabler.kt
@@ -0,0 +1,50 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.debugger.impl.hotswap
+
+import com.intellij.debugger.engine.JavaDebugProcess
+import com.intellij.debugger.impl.DebuggerSession
+import com.intellij.debugger.ui.HotSwapStatusListener
+import com.intellij.xdebugger.XDebugProcess
+import com.intellij.xdebugger.impl.hotswap.HotSwapDebugSessionManager
+import com.intellij.xdebugger.impl.hotswap.HotSwapInDebugSessionEnabler
+import com.intellij.xdebugger.impl.hotswap.HotSwapProvider
+import com.intellij.xdebugger.impl.hotswap.HotSwapResultListener
+
+private class JvmHotSwapInDebugSessionEnabler : HotSwapInDebugSessionEnabler {
+ override fun createProvider(process: XDebugProcess): HotSwapProvider<*>? {
+ val session = (process as? JavaDebugProcess)?.debuggerSession ?: return null
+ return JvmHotSwapProvider(session)
+ }
+}
+
+internal fun createHotSwapSessionListenerOrNull(session: DebuggerSession): HotSwapStatusListener? {
+ val process = session.xDebugSession?.debugProcess ?: return null
+ val hotSwapSession = HotSwapDebugSessionManager.getInstance(session.project).findHotSwapSession(process) ?: return null
+ return HotSwapStatusListenerAdapter(session, hotSwapSession.startHotSwapListening())
+}
+
+private class HotSwapStatusListenerAdapter(private val originalSession: DebuggerSession, private val listener: HotSwapResultListener) : HotSwapStatusListener {
+ override fun onSuccess(sessions: MutableList?) {
+ sessions?.createListeners(originalSession)?.forEach { it.onSuccessfulReload() }
+ listener.onSuccessfulReload()
+ }
+
+ override fun onFailure(sessions: MutableList?) {
+ sessions?.createListeners(originalSession)?.forEach { it.onFailure() }
+ listener.onFailure()
+ }
+
+ override fun onCancel(sessions: MutableList?) {
+ sessions?.createListeners(originalSession)?.forEach { it.onCanceled() }
+ listener.onCanceled()
+ }
+
+ override fun onNothingToReload(sessions: MutableList?) {
+ sessions?.createListeners(originalSession)?.forEach { it.onFinish() }
+ listener.onFinish()
+ }
+
+ private fun List.createListeners(except: DebuggerSession) = filter { it !== except }
+ .mapNotNull { HotSwapDebugSessionManager.getInstance(except.project).findHotSwapSession(it.xDebugSession!!.debugProcess) }
+ .map { it.startHotSwapListening() }
+}
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java
index f01aa74b255b..41b83ad4c8da 100644
--- a/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java
+++ b/java/debugger/impl/src/com/intellij/debugger/ui/HotSwapUIImpl.java
@@ -7,7 +7,7 @@ import com.intellij.debugger.impl.DebuggerManagerListener;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.impl.HotSwapFile;
import com.intellij.debugger.impl.HotSwapManager;
-import com.intellij.debugger.impl.hotswap.HotSwapDebugSessionManager;
+import com.intellij.debugger.impl.hotswap.JvmHotSwapInDebugSessionEnablerKt;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
@@ -295,7 +295,7 @@ public final class HotSwapUIImpl extends HotSwapUI {
@Nullable HotSwapStatusListener callback) {
dontAskHotswapAfterThisCompilation();
Project project = session.getProject();
- callback = mergeCallbacksIfNeeded(callback, HotSwapDebugSessionManager.getInstance(project).createSessionListenerOrNull(session));
+ callback = mergeCallbacksIfNeeded(callback, JvmHotSwapInDebugSessionEnablerKt.createHotSwapSessionListenerOrNull(session));
if (compileBeforeHotswap) {
ProjectTaskManagerImpl.putBuildOriginator(project, this.getClass());
ProjectTaskManager projectTaskManager = ProjectTaskManager.getInstance(project);
@@ -323,7 +323,7 @@ public final class HotSwapUIImpl extends HotSwapUI {
Project project = session.getProject();
ProjectTaskManagerImpl.putBuildOriginator(project, this.getClass());
- HotSwapStatusListener callback = HotSwapDebugSessionManager.getInstance(project).createSessionListenerOrNull(session);
+ HotSwapStatusListener callback = JvmHotSwapInDebugSessionEnablerKt.createHotSwapSessionListenerOrNull(session);
if (callback == null) {
ProjectTaskManager.getInstance(project).compile(files);
}
@@ -399,9 +399,8 @@ public final class HotSwapUIImpl extends HotSwapUI {
boolean alwaysDoHotSwapOnRebuild =
instance.myAskBeforeHotswap && DebuggerSettings.RUN_HOTSWAP_ALWAYS.equals(DebuggerSettings.getInstance().RUN_HOTSWAP_AFTER_COMPILE);
List sessions = getHotSwappableDebugSessions(myProject);
- HotSwapDebugSessionManager manager = HotSwapDebugSessionManager.getInstance(myProject);
for (DebuggerSession session : sessions) {
- HotSwapStatusListener listener = manager.createSessionListenerOrNull(session);
+ HotSwapStatusListener listener = JvmHotSwapInDebugSessionEnablerKt.createHotSwapSessionListenerOrNull(session);
if (listener == null) continue;
context.putUserData(HOT_SWAP_CALLBACK_KEY, listener);
if (alwaysDoHotSwapOnRebuild) {
diff --git a/platform/xdebugger-impl/resources/META-INF/xdebugger.xml b/platform/xdebugger-impl/resources/META-INF/xdebugger.xml
index e16ec196e496..1e36e4a4f7a0 100644
--- a/platform/xdebugger-impl/resources/META-INF/xdebugger.xml
+++ b/platform/xdebugger-impl/resources/META-INF/xdebugger.xml
@@ -28,6 +28,7 @@
+
@@ -121,6 +122,7 @@
+
diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/HotSwapDebugSessionManager.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/HotSwapDebugSessionManager.kt
new file mode 100644
index 000000000000..5a27f7976dcf
--- /dev/null
+++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/HotSwapDebugSessionManager.kt
@@ -0,0 +1,66 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.xdebugger.impl.hotswap
+
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import com.intellij.openapi.components.serviceAsync
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.startup.ProjectActivity
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.registry.Registry
+import com.intellij.util.asDisposable
+import com.intellij.xdebugger.XDebugProcess
+import com.intellij.xdebugger.XDebugSession
+import com.intellij.xdebugger.XDebuggerManager
+import com.intellij.xdebugger.XDebuggerManagerListener
+import com.intellij.xdebugger.impl.XDebugSessionImpl
+import kotlinx.coroutines.CoroutineScope
+import org.jetbrains.annotations.ApiStatus
+import java.util.concurrent.ConcurrentHashMap
+
+
+@ApiStatus.Internal
+@Service(Service.Level.PROJECT)
+class HotSwapDebugSessionManager(project: Project, cs: CoroutineScope) : XDebuggerManagerListener {
+ private val sessions = ConcurrentHashMap()
+
+ init {
+ project.getMessageBus().connect(cs).subscribe(XDebuggerManager.TOPIC, this)
+ }
+
+ fun findHotSwapSession(process: XDebugProcess): HotSwapSession<*>? = sessions[process]?.hotSwapSession
+
+ override fun processStarted(debugProcess: XDebugProcess) {
+ if (!Registry.`is`("debugger.hotswap.floating.toolbar")) return
+ val provider = HotSwapInDebugSessionEnabler.createProviderForProcess(debugProcess) ?: return
+ val xDebugSession = debugProcess.session as XDebugSessionImpl
+ val disposable = xDebugSession.coroutineScope.asDisposable()
+ val hotSwapSession = HotSwapSessionManager.getInstance(xDebugSession.project).createSession(provider, disposable)
+ sessions[debugProcess] = HotSwapSessionEntry(hotSwapSession, disposable)
+ }
+
+ override fun processStopped(debugProcess: XDebugProcess) {
+ val sessionEntry = sessions.remove(debugProcess) ?: return
+ Disposer.dispose(sessionEntry.disposable)
+ }
+
+ override fun currentSessionChanged(previousSession: XDebugSession?, currentSession: XDebugSession?) {
+ if (currentSession == null) return
+ val (_, entry) = sessions.entries.firstOrNull { (process, _) -> process.session === currentSession } ?: return
+ HotSwapSessionManager.getInstance(currentSession.project).onSessionSelected(entry.hotSwapSession)
+ }
+
+ companion object {
+ @JvmStatic
+ fun getInstance(project: Project): HotSwapDebugSessionManager = project.service()
+ }
+}
+
+private data class HotSwapSessionEntry(val hotSwapSession: HotSwapSession<*>, val disposable: Disposable)
+
+private class HotSwapManagerInitActivity : ProjectActivity {
+ override suspend fun execute(project: Project) {
+ project.serviceAsync()
+ }
+}
\ No newline at end of file
diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/api.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/api.kt
index ad280493c3bf..cc59480e86de 100644
--- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/api.kt
+++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/hotswap/api.kt
@@ -2,6 +2,8 @@
package com.intellij.xdebugger.impl.hotswap
import com.intellij.openapi.Disposable
+import com.intellij.openapi.extensions.ExtensionPointName
+import com.intellij.xdebugger.XDebugProcess
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.annotations.ApiStatus
@@ -9,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus
* Provides platform-specific implementation for the hot swap process.
*
* The provider should be passed to [HotSwapSessionManager.createSession] on the start of a session where hot swapping is possible.
+ *
+ * @see [HotSwapInDebugSessionEnabler]
*/
@ApiStatus.Internal
interface HotSwapProvider {
@@ -34,6 +38,22 @@ interface HotSwapProvider {
fun performHotSwap(session: HotSwapSession)
}
+/**
+ * Implement this to enable hotswap in debug sessions.
+ */
+@ApiStatus.Internal
+interface HotSwapInDebugSessionEnabler {
+ fun createProvider(process: XDebugProcess): HotSwapProvider<*>?
+
+ companion object {
+ private val EP_NAME = ExtensionPointName("com.intellij.xdebugger.hotSwapInDebugSessionEnabler")
+
+ internal fun createProviderForProcess(process: XDebugProcess): HotSwapProvider<*>? {
+ return EP_NAME.computeSafeIfAny { it.createProvider(process) }
+ }
+ }
+}
+
/**
* Listener to report the hot swap status.
* @see HotSwapSession.startHotSwapListening