diff --git a/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java b/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java
index ad7e3b7265e6..59e3027ea779 100644
--- a/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java
+++ b/platform/core-api/src/com/intellij/openapi/module/ModuleServiceManager.java
@@ -23,6 +23,18 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
+ * Provide a module service if you need to can be used to store data associated with a module. Its implementation should be registered in plugin.xml:
+ *
+ * <extensions defaultExtensionNs="com.intellij">
+ * <moduleService serviceInterface="qualified-interface-class-name"
+ serviceImplementation="qualified-implementation-class-name"/>
+ * </extensions>
+ *
+ * Class is loaded and its instance is created lazily when {@link #getService(Module, Class)} method is called for the first time.
+ *
+ * If the service implementation class implements {@link com.intellij.openapi.components.PersistentStateComponent} interface its state will
+ * be persisted in the module configuration file.
+ *
* @author yole
*/
public class ModuleServiceManager {
diff --git a/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java b/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java
index 7f5a2319066c..83af5e35cde3 100644
--- a/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java
+++ b/platform/projectModel-api/src/com/intellij/openapi/roots/ModuleExtension.java
@@ -22,7 +22,11 @@ import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
/**
- * Implement {@link com.intellij.openapi.components.PersistentStateComponent} to be serializable.
+ * Extend this class to provide additional module-level properties which can be edited in Project Structure dialog. For ordinary module-level
+ * properties use {@link com.intellij.openapi.module.ModuleServiceManager module service} instead.
+ *
+ * If the inheritor implements {@link com.intellij.openapi.components.PersistentStateComponent} its state will be persisted in the module
+ * configuration file.
*/
public abstract class ModuleExtension implements Disposable {
public static final ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.moduleExtension");
diff --git a/platform/script-debugger/backend/src/SuspendContext.kt b/platform/script-debugger/backend/src/SuspendContext.kt
index 1c9cb81129eb..39fc56e98e95 100755
--- a/platform/script-debugger/backend/src/SuspendContext.kt
+++ b/platform/script-debugger/backend/src/SuspendContext.kt
@@ -58,9 +58,6 @@ interface SuspendContext {
val vm: Vm
get() = throw UnsupportedOperationException()
-
- val workerId: String?
- get() = null
}
abstract class ContextDependentAsyncResultConsumer(private val context: SuspendContext<*>) : Consumer {
diff --git a/platform/script-debugger/backend/src/SuspendContextManagerBase.kt b/platform/script-debugger/backend/src/SuspendContextManagerBase.kt
index 992a02cdf92a..7df95c55e5ab 100644
--- a/platform/script-debugger/backend/src/SuspendContextManagerBase.kt
+++ b/platform/script-debugger/backend/src/SuspendContextManagerBase.kt
@@ -55,7 +55,7 @@ abstract class SuspendContextManagerBase, CAL
throw IllegalStateException("Expected $context, but another suspend context exists")
}
context.valueManager.markObsolete()
- debugListener.resumed()
+ debugListener.resumed(context.vm)
}
override val context: SuspendContext?
diff --git a/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java b/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java
index 764091b57961..c32bf65f9410 100755
--- a/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java
+++ b/platform/script-debugger/backend/src/org/jetbrains/debugger/DebugEventListener.java
@@ -32,8 +32,9 @@ public interface DebugEventListener extends EventListener {
/**
* Reports the virtual machine has resumed. This can happen
* asynchronously, due to a user action in the browser (without explicitly resuming the VM through
+ * @param vm
*/
- default void resumed() {
+ default void resumed(Vm vm) {
}
/**
diff --git a/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt
index 620d31075949..88ddcb3973b9 100644
--- a/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt
+++ b/platform/script-debugger/debugger-ui/src/DebugProcessImpl.kt
@@ -135,7 +135,7 @@ abstract class DebugProcessImpl>(session: XDebugSession,
}
val XSuspendContext?.vm: Vm
- get() = (this as? SuspendContextView)?.activeExecutionStack?.suspendContext?.vm ?: mainVm!!
+ get() = (this as? SuspendContextView)?.activeVm ?: mainVm!!
override final fun startForceStepInto(context: XSuspendContext?) {
isForceStep = true
diff --git a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt
index 183adc41ffad..a81a30980381 100644
--- a/platform/script-debugger/debugger-ui/src/SuspendContextView.kt
+++ b/platform/script-debugger/debugger-ui/src/SuspendContextView.kt
@@ -16,6 +16,7 @@
package org.jetbrains.debugger
import com.intellij.icons.AllIcons
+import com.intellij.openapi.diagnostic.logger
import com.intellij.ui.ColoredTextContainer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ui.UIUtil
@@ -30,8 +31,6 @@ import org.jetbrains.debugger.frame.CallFrameView
import org.jetbrains.debugger.values.StringValue
import java.util.*
-const val MAIN_LOOP_NAME = "main loop"
-
/**
* Debugging several VMs simultaneously should be similar to debugging multi-threaded Java application when breakpoints suspend only one thread.
* 1. When thread is paused and another thread reaches breakpoint, show notification about it with possibility to switch thread.
@@ -41,46 +40,48 @@ const val MAIN_LOOP_NAME = "main loop"
* 4. Stepping/releasing updates current thread icon and clears frame, but doesn't switch thread. To release other threads, user needs to
* select them firstly.
*/
-abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess, protected val activeStack: ExecutionStackView) : XSuspendContext() {
+abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProcess,
+ activeStack: ExecutionStackView,
+ @Volatile var activeVm: Vm)
+ : XSuspendContext() {
- protected open val stacks: Array by lazy {
+ private val stacks: MutableMap = Collections.synchronizedMap(LinkedHashMap())
+
+ init {
val mainVm = debugProcess.mainVm
val vmList = debugProcess.collectVMs
if (mainVm != null && !vmList.isEmpty()) {
- val list = ArrayList()
-
// main vm should go first
- vmList.mapNotNullTo(list) {
+ vmList.forEach {
val context = it.suspendContextManager.context
- if (context == activeStack.suspendContext) {
- activeStack
- }
- else {
- createStackView(context, it.presentableName)
- }
+
+ val stack: ScriptExecutionStack =
+ if (context == null) {
+ RunningThreadExecutionStackView(it)
+ }
+ else if (context == activeStack.suspendContext) {
+ activeStack
+ }
+ else {
+ logger().error("Paused VM was lost.")
+ InactiveAtBreakpointExecutionStackView(it)
+ }
+ stacks[it] = stack
}
- list.toTypedArray()
}
else {
- arrayOf(activeStack)
+ stacks[activeVm] = activeStack
}
}
- private fun createStackView(context: SuspendContext<*>?, displayName: String): XExecutionStack {
- return if (context == null) {
- RunningThreadExecutionStackView(displayName)
- }
- else {
- ExecutionStackView(context, activeStack.viewSupport, null, null, displayName)
- }
- }
+ override fun getActiveExecutionStack() = stacks[activeVm]
- override fun getActiveExecutionStack() = activeStack
-
- override fun getExecutionStacks(): Array = stacks
+ override fun getExecutionStacks(): Array = stacks.values.toTypedArray()
fun evaluateExpression(expression: String): Promise {
+ val activeStack = stacks[activeVm]!!
val frame = activeStack.topFrame ?: return rejectedPromise("Top frame is null")
+ if (frame !is CallFrameView) return rejectedPromise("Can't evaluate on non-paused thread")
return evaluateExpression(frame.callFrame.evaluateContext, expression)
}
@@ -94,9 +95,57 @@ abstract class SuspendContextView(protected val debugProcess: MultiVmDebugProces
resolvedPromise(value.valueString!!)
}
}
+
+ fun pauseInactiveThread(inactiveThread: ExecutionStackView) {
+ stacks[inactiveThread.vm] = inactiveThread
+ }
+
+ fun hasPausedThreads(): Boolean {
+ return stacks.values.any { it is ExecutionStackView }
+ }
+
+ fun resume(vm: Vm) {
+ val prevStack = stacks[vm]
+ if (prevStack is ExecutionStackView) {
+ stacks[vm] = RunningThreadExecutionStackView(prevStack.vm)
+ }
+ }
+
+ fun resumeCurrentThread() {
+ resume(activeVm)
+ }
+
+ fun setActiveThread(selectedStackFrame: XStackFrame?): Boolean {
+ if (selectedStackFrame !is CallFrameView) return false
+
+ var selectedVm: Vm? = null
+ for ((key, value) in stacks) {
+ if (value is ExecutionStackView && value.topFrame?.vm == selectedStackFrame.vm) {
+ selectedVm = key
+ break
+ }
+ }
+
+ val selectedVmStack = stacks[selectedVm]
+ if (selectedVm != null && selectedVmStack is ExecutionStackView) {
+ activeVm = selectedVm
+ stacks[selectedVm] = selectedVmStack.copyWithIsCurrent(true)
+
+ stacks.keys.forEach {
+ val stack = stacks[it]
+ if (it != selectedVm && stack is ExecutionStackView) {
+ stacks[it] = stack.copyWithIsCurrent(false)
+ }
+ }
+
+ return stacks[selectedVm] !== selectedVmStack
+ }
+
+ return false
+ }
}
-class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(displayName, AllIcons.Debugger.ThreadRunning) {
+class RunningThreadExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadRunning) {
override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {
// add dependency to DebuggerBundle?
container?.errorOccurred("Frames not available for unsuspended thread")
@@ -105,12 +154,33 @@ class RunningThreadExecutionStackView(displayName: String) : XExecutionStack(dis
override fun getTopFrame(): XStackFrame? = null
}
-// icon ThreadCurrent would be preferred for active thread, but it won't be updated on stack change
+class InactiveAtBreakpointExecutionStackView(vm: Vm) : ScriptExecutionStack(vm, vm.presentableName, AllIcons.Debugger.ThreadAtBreakpoint) {
+ override fun getTopFrame(): XStackFrame? = null
+
+ override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer?) {}
+}
+
+abstract class ScriptExecutionStack(val vm: Vm, displayName: String, icon: javax.swing.Icon): XExecutionStack(displayName, icon) {
+ override fun hashCode(): Int {
+ return vm.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is ScriptExecutionStack && other.vm == vm
+ }
+}
+
+// TODO should be AllIcons.Debugger.ThreadCurrent, but because of strange logic to add non-equal XExecutionStacks we can't update icon.
+private fun getThreadIcon(isCurrent: Boolean) = AllIcons.Debugger.ThreadAtBreakpoint
+
class ExecutionStackView(val suspendContext: SuspendContext<*>,
internal val viewSupport: DebuggerViewSupport,
private val topFrameScript: Script?,
private val topFrameSourceInfo: SourceInfo? = null,
- displayName: String = "") : XExecutionStack(displayName, AllIcons.Debugger.ThreadAtBreakpoint) {
+ displayName: String = "",
+ isCurrent: Boolean = true)
+ : ScriptExecutionStack(suspendContext.vm, displayName, getThreadIcon(isCurrent)) {
+
private var topCallFrameView: CallFrameView? = null
override fun getTopFrame(): CallFrameView? {
@@ -159,6 +229,12 @@ class ExecutionStackView(val suspendContext: SuspendContext<*>,
container.addStackFrames(result, true)
}
}
+
+ fun copyWithIsCurrent(isCurrent: Boolean): ExecutionStackView {
+ if (icon == getThreadIcon(isCurrent)) return this
+
+ return ExecutionStackView(suspendContext, viewSupport, topFrameScript, topFrameSourceInfo, displayName, isCurrent)
+ }
}
private val PREFIX_ATTRIBUTES = SimpleTextAttributes(SimpleTextAttributes.STYLE_ITALIC, UIUtil.getInactiveTextColor())