mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 09:19:13 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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:
|
||||
* <pre>
|
||||
* <extensions defaultExtensionNs="com.intellij">
|
||||
* <moduleService serviceInterface="qualified-interface-class-name"
|
||||
serviceImplementation="qualified-implementation-class-name"/>
|
||||
* </extensions>
|
||||
* </pre>
|
||||
* Class is loaded and its instance is created lazily when {@link #getService(Module, Class)} method is called for the first time.
|
||||
* <p/>
|
||||
* 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 {
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* 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<ModuleExtension> EP_NAME = ExtensionPointName.create("com.intellij.moduleExtension");
|
||||
|
||||
@@ -58,9 +58,6 @@ interface SuspendContext<CALL_FRAME : CallFrame> {
|
||||
|
||||
val vm: Vm
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
val workerId: String?
|
||||
get() = null
|
||||
}
|
||||
|
||||
abstract class ContextDependentAsyncResultConsumer<T>(private val context: SuspendContext<*>) : Consumer<T> {
|
||||
|
||||
@@ -55,7 +55,7 @@ abstract class SuspendContextManagerBase<T : SuspendContextBase<CALL_FRAME>, CAL
|
||||
throw IllegalStateException("Expected $context, but another suspend context exists")
|
||||
}
|
||||
context.valueManager.markObsolete()
|
||||
debugListener.resumed()
|
||||
debugListener.resumed(context.vm)
|
||||
}
|
||||
|
||||
override val context: SuspendContext<CALL_FRAME>?
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -135,7 +135,7 @@ abstract class DebugProcessImpl<C : VmConnection<*>>(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
|
||||
|
||||
@@ -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<out XExecutionStack> by lazy {
|
||||
private val stacks: MutableMap<Vm, ScriptExecutionStack> = Collections.synchronizedMap(LinkedHashMap<Vm, ScriptExecutionStack>())
|
||||
|
||||
init {
|
||||
val mainVm = debugProcess.mainVm
|
||||
val vmList = debugProcess.collectVMs
|
||||
if (mainVm != null && !vmList.isEmpty()) {
|
||||
val list = ArrayList<XExecutionStack>()
|
||||
|
||||
// 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<SuspendContextView>().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<out XExecutionStack> = stacks
|
||||
override fun getExecutionStacks(): Array<out XExecutionStack> = stacks.values.toTypedArray()
|
||||
|
||||
fun evaluateExpression(expression: String): Promise<String> {
|
||||
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())
|
||||
|
||||
Reference in New Issue
Block a user