mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] IDEA-358626 Show indicator with possibility to cancel the stepping request
IJ-MR-145237 GitOrigin-RevId: 4283b9faf567bdbcaf25a44319928e102e3a7b10
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c84dac7d1e
commit
0729b2a230
@@ -156,6 +156,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
|
||||
final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, disposable);
|
||||
|
||||
final SteppingProgressTracker mySteppingProgressTracker = new SteppingProgressTracker(this);
|
||||
|
||||
// These 2 fields are needs to switching from found suspend-thread context to user-friendly suspend-all context.
|
||||
// The main related logic is in [SuspendOtherThreadsRequestor].
|
||||
volatile ParametersForSuspendAllReplacing myParametersForSuspendAllReplacing = null;
|
||||
@@ -197,6 +199,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
DebuggerStatistics.logProcessStatistics(process);
|
||||
}
|
||||
});
|
||||
mySteppingProgressTracker.installListeners();
|
||||
}
|
||||
|
||||
private DebuggerManagerThreadImpl createManagerThread() {
|
||||
@@ -2262,7 +2265,10 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
}
|
||||
|
||||
private class PauseCommand extends DebuggerCommandImpl {
|
||||
PauseCommand() {
|
||||
@Nullable private final ThreadReferenceProxyImpl myPredefinedThread;
|
||||
|
||||
PauseCommand(@Nullable ThreadReferenceProxyImpl thread) {
|
||||
myPredefinedThread = thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2274,6 +2280,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
getVirtualMachineProxy().suspend();
|
||||
logThreads();
|
||||
SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0);
|
||||
if (myPredefinedThread != null) {
|
||||
suspendContext.setThread(myPredefinedThread.getThreadReference());
|
||||
}
|
||||
myDebugProcessListeners.forEach(it -> it.paused(suspendContext));
|
||||
|
||||
myDebuggerManagerThread.schedule(new SuspendContextCommandImpl(suspendContext) {
|
||||
@@ -2697,8 +2706,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DebuggerCommandImpl createPauseCommand() {
|
||||
return new PauseCommand();
|
||||
public DebuggerCommandImpl createPauseCommand(@Nullable ThreadReferenceProxyImpl threadProxy) {
|
||||
return new PauseCommand(threadProxy);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -7,11 +7,7 @@ import com.intellij.debugger.engine.events.SuspendContextCommandImpl
|
||||
import com.intellij.debugger.engine.managerThread.DebuggerCommand
|
||||
import com.intellij.debugger.engine.managerThread.DebuggerManagerThread
|
||||
import com.intellij.debugger.engine.managerThread.SuspendContextCommand
|
||||
import com.intellij.debugger.impl.DebuggerCompletableFuture
|
||||
import com.intellij.debugger.impl.DebuggerUtilsAsync
|
||||
import com.intellij.debugger.impl.InvokeAndWaitThread
|
||||
import com.intellij.debugger.impl.InvokeThread
|
||||
import com.intellij.debugger.impl.PrioritizedTask
|
||||
import com.intellij.debugger.impl.*
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ComponentManagerEx
|
||||
@@ -21,16 +17,19 @@ import com.intellij.openapi.progress.util.ProgressIndicatorListener
|
||||
import com.intellij.openapi.progress.util.ProgressWindow
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsContexts.ProgressTitle
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: CoroutineScope) :
|
||||
InvokeAndWaitThread<DebuggerCommandImpl?>(), DebuggerManagerThread, Disposable {
|
||||
@@ -53,6 +52,24 @@ class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: Cor
|
||||
myDisposed = true
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun makeCancelable(project: Project, progressTitle: @ProgressTitle String, progressText: @Nls String, howToCancel: () -> Unit): CompletableDeferred<Unit> {
|
||||
val deferred = CompletableDeferred<Unit>()
|
||||
coroutineScope.launch {
|
||||
withBackgroundProgress(project, progressTitle) {
|
||||
withProgressText(progressText) {
|
||||
try {
|
||||
deferred.await()
|
||||
} catch (e: CancellationException) {
|
||||
howToCancel()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deferred
|
||||
}
|
||||
|
||||
private fun createScope() = parentScope.childScope("DebuggerManagerThreadImpl")
|
||||
|
||||
override fun invokeAndWait(managerCommand: DebuggerCommandImpl) {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.debugger.engine
|
||||
|
||||
import com.intellij.debugger.JavaDebuggerBundle
|
||||
import com.sun.jdi.ThreadReference
|
||||
|
||||
interface LightOrRealThreadInfo {
|
||||
val realThread: ThreadReference?
|
||||
|
||||
fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean
|
||||
|
||||
val filterName: String
|
||||
}
|
||||
|
||||
data class RealThreadInfo(override val realThread: ThreadReference) : LightOrRealThreadInfo {
|
||||
override fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean {
|
||||
return realThread == thread
|
||||
}
|
||||
|
||||
override val filterName: String get() = JavaDebuggerBundle.message("stepping.filter.real.thread.name", realThread.name())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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.engine
|
||||
|
||||
import com.intellij.debugger.JavaDebuggerBundle
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.engine.jdi.ThreadReferenceProxy
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
private data class TrackedSteppingData(val stepCompetedStatus: CompletableDeferred<Unit>, val threadFilter: (ThreadReferenceProxy?) -> Boolean)
|
||||
|
||||
private class SteppingProgressTracker(private val debuggerProcessImpl: DebugProcessImpl) {
|
||||
private val trackedStepping = mutableListOf<TrackedSteppingData>()
|
||||
|
||||
fun installListeners() {
|
||||
debuggerProcessImpl.addDebugProcessListener(object : DebugProcessListener {
|
||||
override fun paused(suspendContext: SuspendContext) {
|
||||
val thread = suspendContext.thread
|
||||
val completedSteps = trackedStepping.filter { it.threadFilter(thread) }
|
||||
for ((stepCompetedStatus, _) in completedSteps) {
|
||||
stepCompetedStatus.complete(Unit)
|
||||
}
|
||||
trackedStepping.removeAll(completedSteps)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun addStepping(stepCompetedStatus: CompletableDeferred<Unit>, threadFilter: (ThreadReferenceProxy?) -> Boolean) {
|
||||
trackedStepping.add(TrackedSteppingData(stepCompetedStatus, threadFilter))
|
||||
}
|
||||
}
|
||||
|
||||
private class CancelingSteppingListener : SteppingListener {
|
||||
override fun beforeSteppingStarted(suspendContext: SuspendContextImpl, steppingAction: SteppingAction) {
|
||||
val debuggerProcessImpl: DebugProcessImpl = suspendContext.debugProcess
|
||||
val filter: LightOrRealThreadInfo? = debuggerProcessImpl.requestsManager.filterThread
|
||||
|
||||
val threadForStepping: ThreadReferenceProxyImpl? =
|
||||
if (filter != null) debuggerProcessImpl.virtualMachineProxy.getThreadReferenceProxy(filter.realThread)
|
||||
else suspendContext.thread
|
||||
|
||||
val needSuspendOnlyThread = suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD && threadForStepping != null
|
||||
|
||||
val whereStr =
|
||||
if (threadForStepping != null) JavaDebuggerBundle.message("stepping.filter.real.thread.name", threadForStepping.name())
|
||||
else filter?.filterName ?: debuggerProcessImpl.session.sessionName
|
||||
|
||||
val steppingRestrictionMessage = getSteppingRestrictionMessage(whereStr, steppingAction)
|
||||
val steppingName = steppingAction.steppingName
|
||||
val stepCompetedStatus = debuggerProcessImpl.managerThread.makeCancelable(debuggerProcessImpl.project, steppingRestrictionMessage, steppingName) {
|
||||
val command: DebuggerCommandImpl =
|
||||
if (needSuspendOnlyThread) debuggerProcessImpl.createFreezeThreadCommand(threadForStepping)
|
||||
else debuggerProcessImpl.createPauseCommand(threadForStepping)
|
||||
debuggerProcessImpl.managerThread.schedule(command)
|
||||
}
|
||||
|
||||
val tracker = suspendContext.debugProcess.mySteppingProgressTracker
|
||||
tracker.addStepping(stepCompetedStatus) { thread ->
|
||||
!needSuspendOnlyThread || thread == null || thread == threadForStepping
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val SteppingAction.steppingName: @Nls String get() = when (this) {
|
||||
SteppingAction.STEP_INTO -> JavaDebuggerBundle.message("status.step.into")
|
||||
SteppingAction.STEP_OUT -> JavaDebuggerBundle.message("status.step.out")
|
||||
SteppingAction.STEP_OVER -> JavaDebuggerBundle.message("status.step.over")
|
||||
SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor")
|
||||
}
|
||||
|
||||
private fun getSteppingRestrictionMessage(whereSteppingIsPerformed: @Nls String?, steppingAction: SteppingAction): @Nls String {
|
||||
return when (steppingAction) {
|
||||
SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor.in", whereSteppingIsPerformed)
|
||||
else -> JavaDebuggerBundle.message("status.stepping.in", whereSteppingIsPerformed)
|
||||
}
|
||||
}
|
||||
@@ -409,7 +409,7 @@ public final class DebuggerSession implements AbstractDebuggerSession {
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand());
|
||||
myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand(null));
|
||||
}
|
||||
|
||||
/*Presentation*/
|
||||
|
||||
@@ -95,6 +95,8 @@ status.step.out=Stepping out
|
||||
status.step.into=Stepping into
|
||||
status.step.over=Stepping over
|
||||
status.run.to.cursor=Run to cursor
|
||||
status.run.to.cursor.in=Run to cursor in {0}
|
||||
status.stepping.in=Stepping in {0}
|
||||
status.process.resumed=Process resumed
|
||||
status.paused.in.another.thread=Paused in another thread
|
||||
string.connection=''{0}'', transport: ''{1}''
|
||||
@@ -548,3 +550,4 @@ paths.to.closest.gc.roots.for=Paths to the Closest GC Roots for {0}
|
||||
column.name.instances=Instances
|
||||
column.name.shallow.size=Shallow Size
|
||||
column.name.retained.size=Retained Size
|
||||
stepping.filter.real.thread.name=thread {0}
|
||||
|
||||
@@ -2883,6 +2883,8 @@
|
||||
</extensions>
|
||||
|
||||
<applicationListeners>
|
||||
<listener class="com.intellij.debugger.engine.CancelingSteppingListener"
|
||||
topic="com.intellij.debugger.engine.SteppingListener"/>
|
||||
<listener class="com.intellij.codeInsight.daemon.problems.pass.ProjectProblemFileFileEditorManagerListener"
|
||||
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
|
||||
<listener class="com.intellij.psi.impl.cache.impl.idCache.JavaIdIndexRegistryValueListener"
|
||||
|
||||
+4
-1
@@ -47,4 +47,7 @@ property.watchpoint.add.dialog.choose.property.label=Property name:
|
||||
property.watchpoint.add.dialog.chooser.title={0,choice, 0#Class Has No Properties|1#Select Property}
|
||||
|
||||
progress.title.kt.file.search=Debugger: Searching for Kotlin file
|
||||
progress.text.waiting.for.smart.mode=Waiting for indexing to complete
|
||||
progress.text.waiting.for.smart.mode=Waiting for indexing to complete
|
||||
|
||||
stepping.filter.coroutine.name=coroutine {0}
|
||||
stepping.filter.several.coroutines.name=coroutines {0}
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.idea.debugger.core.StackFrameInterceptor
|
||||
|
||||
interface CoroutineFilter {
|
||||
fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean
|
||||
val coroutineFilterName: String
|
||||
}
|
||||
|
||||
data class CoroutineJobInfo(private val coroutineFilter: CoroutineFilter) : LightOrRealThreadInfo {
|
||||
@@ -22,6 +23,8 @@ data class CoroutineJobInfo(private val coroutineFilter: CoroutineFilter) : Ligh
|
||||
return nextCoroutineFilter != null && coroutineFilter.canRunTo(nextCoroutineFilter)
|
||||
}
|
||||
|
||||
override val filterName get() = coroutineFilter.coroutineFilterName
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
private fun getCoroutineFilter(suspendContext: SuspendContextImpl): CoroutineFilter? {
|
||||
|
||||
+15
-1
@@ -21,11 +21,15 @@ import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionCon
|
||||
import org.jetbrains.kotlin.idea.debugger.base.util.safeLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.base.util.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.base.util.safeMethod
|
||||
import org.jetbrains.kotlin.idea.debugger.core.KotlinDebuggerCoreBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.core.StackFrameInterceptor
|
||||
import org.jetbrains.kotlin.idea.debugger.core.stepping.CoroutineFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendExitMode
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.SkipCoroutineStackFrameProxyImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.CoroutineStackFrameLight
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugMetadata
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.DebugProbesImplCoroutineOwner
|
||||
import org.jetbrains.kotlin.idea.debugger.coroutine.util.*
|
||||
|
||||
|
||||
@@ -254,6 +258,14 @@ class CoroutineStackFrameInterceptor : StackFrameInterceptor {
|
||||
}
|
||||
override fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean =
|
||||
(nextCoroutineFilter is CoroutineIdFilter && coroutinesRunningOnCurrentThread.intersect(nextCoroutineFilter.coroutinesRunningOnCurrentThread).isNotEmpty())
|
||||
|
||||
override val coroutineFilterName: String get() {
|
||||
coroutinesRunningOnCurrentThread.singleOrNull()?.let {
|
||||
return KotlinDebuggerCoreBundle.message("stepping.filter.coroutine.name", "#$it")
|
||||
}
|
||||
val ids = coroutinesRunningOnCurrentThread.toList().sortedBy { it }.joinToString(", ") { "#$it" }
|
||||
return KotlinDebuggerCoreBundle.message("stepping.filter.several.coroutines.name", ids)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,5 +274,7 @@ class CoroutineStackFrameInterceptor : StackFrameInterceptor {
|
||||
private data class ContinuationObjectFilter(val reference: ObjectReference) : CoroutineFilter {
|
||||
override fun canRunTo(nextCoroutineFilter: CoroutineFilter): Boolean =
|
||||
this == nextCoroutineFilter
|
||||
|
||||
override val coroutineFilterName: String get() = reference.toString()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user