[rd debugger] API to access selected XExecutionStack on the backend by id

IJPL-212671

GitOrigin-RevId: 24421e92c4fbd8bc38bead942d9927c2a49cf74e
This commit is contained in:
Maria Sokolova
2025-11-05 23:48:53 +00:00
committed by intellij-monorepo-bot
parent 78988c4694
commit 7ece3ff063
7 changed files with 70 additions and 1 deletions
@@ -46,6 +46,9 @@ private class FrontendXDebugManagerProxy : XDebugManagerProxy {
override fun getXValueId(value: XValue): XValueId? =
FrontendXValue.asFrontendXValueOrNull(value)?.xValueDto?.id
override fun getXExecutionStackId(stack: XExecutionStack): XExecutionStackId? =
(stack as? FrontendXExecutionStack)?.id
override suspend fun <T> withId(stack: XExecutionStack, session: XDebugSessionProxy, block: suspend (XExecutionStackId) -> T): T {
val executionStackId = (stack as FrontendXExecutionStack).id
return block(executionStackId)
@@ -181,6 +181,7 @@
<remote.customDataContextSerializer implementation="com.intellij.xdebugger.impl.ui.RunContentDescriptorKeySerializer"/>
<remote.customDataContextSerializer implementation="com.intellij.xdebugger.impl.ui.ExecutionEnvironmentKeySerializer"/>
<remote.customDataContextSerializer implementation="com.intellij.xdebugger.impl.ui.XDebuggerTreeSelectedValuesSerializer"/>
<remote.customDataContextSerializer implementation="com.intellij.xdebugger.impl.ui.XDebuggerTreeSelectedStacksSerializer"/>
<intentionAction>
<language>UAST</language>
@@ -43,6 +43,9 @@ private class MonolithXDebugManagerProxy : XDebugManagerProxy {
// This method is not supported in monolith mode
override fun getXValueId(value: XValue): XValueId? = null
// This method is not supported in monolith mode
override fun getXExecutionStackId(stack: XExecutionStack): XExecutionStackId? = null
override suspend fun <T> withId(stack: XExecutionStack, session: XDebugSessionProxy, block: suspend (XExecutionStackId) -> T): T {
val sessionImpl = (session as XDebugSessionProxy.Monolith).sessionImpl
return withCoroutineScopeForId(block) { scope ->
@@ -61,6 +61,14 @@ interface XDebugManagerProxy {
*/
fun getXValueId(value: XValue): XValueId?
/**
* Gets ID of the given [stack].
*
* This method is used in split mode to pass the ID of the execution stack from frontend to backend.
* It's not supported in monolith mode.
*/
fun getXExecutionStackId(stack: XExecutionStack): XExecutionStackId?
companion object {
private val EP_NAME = ExtensionPointName.create<XDebugManagerProxy>("com.intellij.xdebugger.managerProxy")
@@ -12,6 +12,8 @@ import com.intellij.ui.popup.list.GroupedItemsListRenderer
import com.intellij.xdebugger.XDebuggerBundle
import com.intellij.xdebugger.frame.XExecutionStack
import com.intellij.xdebugger.frame.XExecutionStack.AdditionalDisplayInfo
import com.intellij.xdebugger.impl.frame.XDebugManagerProxy.Companion.getInstance
import com.intellij.xdebugger.impl.ui.SplitDebuggerUIUtil
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.awt.Component
@@ -61,10 +63,19 @@ class XDebuggerThreadsList(
override fun uiDataSnapshot(sink: DataSink) {
sink[THREADS_LIST] = this
stackUnderMouse?.stack?.let {
sink[XExecutionStack.SELECTED_STACKS] = listOf(it)
setSelectedStacks(sink, it)
}
}
private fun setSelectedStacks(sink: DataSink, selection: XExecutionStack) {
val xDebugManagerProxy = getInstance()
val xExecutionStackId = xDebugManagerProxy.getXExecutionStackId(selection)
if (xExecutionStackId != null) {
sink[SplitDebuggerUIUtil.SPLIT_SELECTED_STACKS_KEY] = listOf(xExecutionStackId)
}
sink[XExecutionStack.SELECTED_STACKS] = listOf(selection)
}
private fun doInit() {
selectionModel.selectionMode = ListSelectionModel.SINGLE_SELECTION
cellRenderer = renderer
@@ -5,12 +5,18 @@ import com.intellij.execution.RunContentDescriptorIdImpl
import com.intellij.execution.findContentValue
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.execution.ui.RunContentDescriptor
import com.intellij.frontend.FrontendApplicationInfo.getFrontendType
import com.intellij.frontend.FrontendType
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.openapi.actionSystem.ExecutionDataKeys
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.debugger.impl.rpc.XDebuggerTreeSelectedValueId
import com.intellij.platform.debugger.impl.rpc.XExecutionStackId
import com.intellij.xdebugger.SplitDebuggerMode
import com.intellij.xdebugger.SplitDebuggerMode.showSplitWarnings
import com.intellij.xdebugger.frame.XExecutionStack
import com.intellij.xdebugger.frame.XValue
import com.intellij.xdebugger.impl.rpc.ExecutionEnvironmentId
import com.intellij.xdebugger.impl.rpc.models.BackendXValueModel
@@ -30,6 +36,14 @@ object SplitDebuggerUIUtil {
@JvmField
internal val SPLIT_SELECTED_VALUES_KEY: DataKey<List<XDebuggerTreeSelectedValueId>> = DataKey.create("split.xdebugger.tree.selected.values")
/**
* This key is used to pass ids of [XExecutionStack]s corresponding to the selected stacks in the tree from frontend to backend.
*/
@JvmField
internal val SPLIT_SELECTED_STACKS_KEY: DataKey<List<XExecutionStackId>> = DataKey.create("split.xExecutionStack.selected.stacks")
private val LOG: Logger = Logger.getInstance(SplitDebuggerUIUtil::class.java)
@JvmStatic
fun getRunContentDescriptor(dataContext: DataContext): RunContentDescriptor? {
LangDataKeys.RUN_CONTENT_DESCRIPTOR.getData(dataContext)?.let { return it }
@@ -44,6 +58,10 @@ object SplitDebuggerUIUtil {
@JvmStatic
fun getXDebuggerTreeSelectedBackendValues(dataContext: DataContext): List<XDebuggerTreeSelectedValue> {
if (showSplitWarnings() && getFrontendType() is FrontendType.Remote) {
LOG.error("SplitDebuggerUIUtil#getXDebuggerTreeSelectedBackendValues should not be called from the frontend. " +
"Please use XDebuggerTree.SELECTED_NODES#getData instead.")
}
return if (SplitDebuggerMode.isSplitDebugger()) {
// In Split mode, find backend values by the ids passed from the frontend
SPLIT_SELECTED_VALUES_KEY.getData(dataContext)?.mapNotNull { (xValueId, name) ->
@@ -57,4 +75,21 @@ object SplitDebuggerUIUtil {
} ?: emptyList()
}
}
@JvmStatic
fun getSelectedBackendXExecutionStacks(dataContext: DataContext): List<XExecutionStack> {
if (showSplitWarnings() && getFrontendType() is FrontendType.Remote) {
LOG.error("SplitDebuggerUIUtil#getSelectedBackendXExecutionStacks should not be called from the frontend. " +
"Please use XExecutionStack.SELECTED_STACKS#getData instead.")
}
return if (SplitDebuggerMode.isSplitDebugger()) {
// In Split mode, find backend XExecutionStacks by the ids passed from the frontend
SPLIT_SELECTED_STACKS_KEY.getData(dataContext)?.mapNotNull { xExecutionStackId ->
xExecutionStackId.findValue()?.executionStack
}
} else {
// If Split mode is disabled, SELECTED_STACKS contains backend values
XExecutionStack.SELECTED_STACKS.getData(dataContext)
} ?: emptyList()
}
}
@@ -5,6 +5,7 @@ import com.intellij.execution.RunContentDescriptorIdImpl
import com.intellij.ide.CustomDataContextSerializer
import com.intellij.openapi.actionSystem.DataKey
import com.intellij.platform.debugger.impl.rpc.XDebuggerTreeSelectedValueId
import com.intellij.platform.debugger.impl.rpc.XExecutionStackId
import com.intellij.xdebugger.impl.rpc.ExecutionEnvironmentId
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
@@ -28,4 +29,11 @@ private class XDebuggerTreeSelectedValuesSerializer : CustomDataContextSerialize
get() = SplitDebuggerUIUtil.SPLIT_SELECTED_VALUES_KEY
override val serializer: KSerializer<List<XDebuggerTreeSelectedValueId>>
get() = ListSerializer(XDebuggerTreeSelectedValueId.serializer())
}
private class XDebuggerTreeSelectedStacksSerializer : CustomDataContextSerializer<List<XExecutionStackId>> {
override val key: DataKey<List<XExecutionStackId>>
get() = SplitDebuggerUIUtil.SPLIT_SELECTED_STACKS_KEY
override val serializer: KSerializer<List<XExecutionStackId>>
get() = ListSerializer(XExecutionStackId.serializer())
}