diff --git a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerEvaluatorApi.kt b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerEvaluatorApi.kt new file mode 100644 index 000000000000..b2231e5c47d1 --- /dev/null +++ b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerEvaluatorApi.kt @@ -0,0 +1,93 @@ +// 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.platform.debugger.impl.backend + +import com.intellij.openapi.application.EDT +import com.intellij.openapi.util.NlsContexts +import com.intellij.platform.kernel.withKernel +import com.intellij.xdebugger.evaluation.XDebuggerEvaluator.XEvaluationCallback +import com.intellij.xdebugger.frame.XFullValueEvaluator +import com.intellij.xdebugger.frame.XValue +import com.intellij.xdebugger.frame.XValueNode +import com.intellij.xdebugger.frame.XValuePlace +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorApi +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorId +import com.intellij.xdebugger.impl.rpc.XValueId +import com.intellij.xdebugger.impl.rpc.XValuePresentation +import com.intellij.xdebugger.impl.ui.tree.nodes.XValuePresentationUtil.XValuePresentationTextExtractor +import com.jetbrains.rhizomedb.entity +import fleet.kernel.change +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.collectLatest +import org.jetbrains.annotations.NonNls +import javax.swing.Icon + +internal class BackendXDebuggerEvaluatorApi : XDebuggerEvaluatorApi { + override suspend fun evaluate(evaluatorId: XDebuggerEvaluatorId, expression: String): Deferred? = withKernel { + val evaluatorEntity = entity(evaluatorId.eid) as? XDebuggerEvaluatorEntity ?: return@withKernel null + val evaluator = evaluatorEntity.evaluator + val evaluationResult = CompletableDeferred() + + withContext(Dispatchers.EDT) { + // TODO: pass SourcePosition + evaluator.evaluate(expression, object : XEvaluationCallback { + override fun evaluated(result: XValue) { + evaluationResult.complete(result) + } + + override fun errorOccurred(errorMessage: @NlsContexts.DialogMessage String) { + // TODO: shouldn't be exception + evaluationResult.completeExceptionally(RuntimeException(errorMessage)) + } + }, null) + } + + // TODO: don't use GlobalScope + GlobalScope.async(Dispatchers.EDT) { + val xValue = evaluationResult.await() + val xValueEntity = withKernel { + change { + // TODO: leaked XValue entity, it is never disposed + LocalHintXValueEntity.new { + it[LocalHintXValueEntity.Project] = evaluatorEntity.projectEntity + it[LocalHintXValueEntity.XValue] = xValue + } + } + } + XValueId(xValueEntity.eid) + } + } + + override suspend fun computePresentation(xValueId: XValueId): Flow? = withKernel { + val hintEntity = entity(xValueId.eid) as? LocalHintXValueEntity ?: return@withKernel null + val presentations = MutableSharedFlow(replay = 1) + val xValue = hintEntity.xValue + channelFlow { + // TODO: mark as Obsolescent when needed + val valueNode = object : XValueNode { + override fun setPresentation(icon: Icon?, type: @NonNls String?, value: @NonNls String, hasChildren: Boolean) { + // TODO: pass icon, type and hasChildren too + presentations.tryEmit(XValuePresentation(value)) + } + + override fun setPresentation(icon: Icon?, presentation: com.intellij.xdebugger.frame.presentation.XValuePresentation, hasChildren: Boolean) { + // TODO: handle XValuePresentation fully + val textExtractor = XValuePresentationTextExtractor() + presentation.renderValue(textExtractor) + setPresentation(icon, presentation.type, textExtractor.text, hasChildren) + } + + override fun setFullValueEvaluator(fullValueEvaluator: XFullValueEvaluator) { + // TODO: implement setFullValueEvaluator + } + } + xValue.computePresentation(valueNode, XValuePlace.TOOLTIP) + + presentations.collectLatest { + send(it) + } + } + } +} \ No newline at end of file diff --git a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerRemoteApiProviders.kt b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerRemoteApiProviders.kt index 8f596ffe12c4..37872a6c5505 100644 --- a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerRemoteApiProviders.kt +++ b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerRemoteApiProviders.kt @@ -2,6 +2,7 @@ package com.intellij.platform.debugger.impl.backend import com.intellij.platform.rpc.backend.RemoteApiProvider +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorApi import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi import fleet.rpc.remoteApiDescriptor @@ -10,5 +11,8 @@ private class BackendXDebuggerRemoteApiProviders : RemoteApiProvider { remoteApi(remoteApiDescriptor()) { BackendXDebuggerValueLookupHintsRemoteApi() } + remoteApi(remoteApiDescriptor()) { + BackendXDebuggerEvaluatorApi() + } } } \ No newline at end of file diff --git a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerValueLookupHintsRemoteApi.kt b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerValueLookupHintsRemoteApi.kt index c5f6cecebbde..4d0186cd216b 100644 --- a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerValueLookupHintsRemoteApi.kt +++ b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/BackendXDebuggerValueLookupHintsRemoteApi.kt @@ -4,9 +4,6 @@ package com.intellij.platform.debugger.impl.backend import com.intellij.codeInsight.TargetElementUtil import com.intellij.openapi.application.EDT import com.intellij.openapi.application.readAction -import com.intellij.openapi.application.runReadAction -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.EditorId import com.intellij.openapi.editor.impl.findEditor @@ -25,6 +22,7 @@ import com.intellij.xdebugger.impl.evaluate.quick.XValueHint import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType import com.intellij.xdebugger.impl.rpc.RemoteValueHintId +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorId import com.intellij.xdebugger.impl.rpc.XDebuggerValueLookupHintsRemoteApi import com.jetbrains.rhizomedb.entity import fleet.kernel.change @@ -133,6 +131,19 @@ internal class BackendXDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupH } } + override suspend fun createHintEvaluator(projectId: ProjectId): XDebuggerEvaluatorId? = withKernel { + // TODO: leaking evaluator, it is created every time and is not disposed + val project = projectId.findProject() + val evaluator = XDebuggerManager.getInstance(project).currentSession!!.debugProcess.evaluator!! + val evaluatorEntity = change { + LocalHintXDebuggerEvaluatorEntity.new { + it[XDebuggerEvaluatorEntity.Project] = project.asEntity() + it[XDebuggerEvaluatorEntity.Evaluator] = evaluator + } + } + XDebuggerEvaluatorId(evaluatorEntity.eid) + } + private suspend fun getValueHintFromDebuggerPlugins( project: Project, editor: Editor, diff --git a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/entities.kt b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/entities.kt index bbb845c797fc..9be4394b82be 100644 --- a/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/entities.kt +++ b/platform/xdebugger-impl/backend/src/com/intellij/platform/debugger/impl/backend/entities.kt @@ -3,21 +3,21 @@ package com.intellij.platform.debugger.impl.backend import com.intellij.platform.kernel.EntityTypeProvider import com.intellij.platform.project.ProjectEntity +import com.intellij.xdebugger.evaluation.XDebuggerEvaluator +import com.intellij.xdebugger.frame.XValue import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint -import com.jetbrains.rhizomedb.EID -import com.jetbrains.rhizomedb.Entity -import com.jetbrains.rhizomedb.EntityType -import org.jetbrains.annotations.ApiStatus +import com.jetbrains.rhizomedb.* private class BackendXDebuggerEntityTypesProvider : EntityTypeProvider { override fun entityTypes(): List> { return listOf( LocalValueHintEntity, + LocalHintXDebuggerEvaluatorEntity, + LocalHintXValueEntity ) } } -@ApiStatus.Internal internal data class LocalValueHintEntity(override val eid: EID) : Entity { val projectEntity by Project val hint by Hint @@ -30,4 +30,40 @@ internal data class LocalValueHintEntity(override val eid: EID) : Entity { val Project = requiredRef("project") val Hint = requiredTransient("hint") } +} + +internal data class LocalHintXDebuggerEvaluatorEntity(override val eid: EID) : XDebuggerEvaluatorEntity { + companion object : EntityType( + LocalHintXDebuggerEvaluatorEntity::class.java.name, + "com.intellij", + ::LocalHintXDebuggerEvaluatorEntity, + XDebuggerEvaluatorEntity + ) +} + +internal interface XDebuggerEvaluatorEntity : Entity { + val projectEntity: ProjectEntity + get() = this[Project] + + val evaluator: XDebuggerEvaluator + get() = this[Evaluator] + + companion object : Mixin(XDebuggerEvaluatorEntity::class) { + val Project = requiredRef("project") + val Evaluator = requiredTransient("evaluator") + } +} + +internal data class LocalHintXValueEntity(override val eid: EID) : Entity { + val projectEntity by Project + val xValue by XValue + + companion object : EntityType( + LocalHintXValueEntity::class.java.name, + "com.intellij", + ::LocalHintXValueEntity + ) { + val Project = requiredRef("project") + val XValue = requiredTransient("xValue") + } } \ No newline at end of file diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXDebuggerEvaluator.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXDebuggerEvaluator.kt new file mode 100644 index 000000000000..86a25237b1c6 --- /dev/null +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXDebuggerEvaluator.kt @@ -0,0 +1,34 @@ +// 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.platform.debugger.impl.frontend.evaluate.quick + +import com.intellij.openapi.application.EDT +import com.intellij.platform.kernel.withKernel +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.evaluation.XDebuggerEvaluator +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorApi +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +// TODO: support XDebuggerPsiEvaluator +internal class FrontendXDebuggerEvaluator(private val scope: CoroutineScope, private val evaluatorId: XDebuggerEvaluatorId) : XDebuggerEvaluator() { + override fun evaluate(expression: String, callback: XEvaluationCallback, expressionPosition: XSourcePosition?) { + scope.launch(Dispatchers.EDT) { + withKernel { + val xValue = try { + // TODO: write proper error message + val xValueId = XDebuggerEvaluatorApi.getInstance().evaluate(evaluatorId, expression) ?: error("Cannot evaluate") + // TODO: what scope to provide for the XValue? + FrontendXValue(scope, xValueId.await()) + } + catch (e: Exception) { + // TODO: write proper error message + callback.errorOccurred(e.message ?: "Error occurred during evaluation") + return@withKernel + } + callback.evaluated(xValue) + } + } + } +} \ No newline at end of file diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXValue.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXValue.kt new file mode 100644 index 000000000000..be2f2f52ccd1 --- /dev/null +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/FrontendXValue.kt @@ -0,0 +1,23 @@ +// 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.platform.debugger.impl.frontend.evaluate.quick + +import com.intellij.openapi.application.EDT +import com.intellij.xdebugger.frame.XValue +import com.intellij.xdebugger.frame.XValueNode +import com.intellij.xdebugger.frame.XValuePlace +import com.intellij.xdebugger.impl.rpc.XDebuggerEvaluatorApi +import com.intellij.xdebugger.impl.rpc.XValueId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +internal class FrontendXValue(private val scope: CoroutineScope, private val xValueId: XValueId) : XValue() { + override fun computePresentation(node: XValueNode, place: XValuePlace) { + scope.launch(Dispatchers.EDT) { + XDebuggerEvaluatorApi.getInstance().computePresentation(xValueId)?.collect { presentation -> + // TODO: pass proper params + node.setPresentation(null, null, presentation.value, false) + } + } + } +} \ No newline at end of file diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/XQuickEvaluateHandler.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/XQuickEvaluateHandler.kt index 6a3ad617fe00..83a572e54955 100644 --- a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/XQuickEvaluateHandler.kt +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/evaluate/quick/XQuickEvaluateHandler.kt @@ -4,6 +4,8 @@ package com.intellij.platform.debugger.impl.frontend.evaluate.quick import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.editorId +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.fileTypes.FileTypes import com.intellij.openapi.project.Project import com.intellij.openapi.util.registry.Registry import com.intellij.platform.debugger.impl.frontend.evaluate.quick.common.RemoteValueHint @@ -14,6 +16,7 @@ import com.intellij.platform.kernel.withKernel import com.intellij.platform.project.asEntity import com.intellij.platform.project.projectId import com.intellij.xdebugger.XDebuggerManager +import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider import com.intellij.xdebugger.impl.XDebuggerActiveSessionEntity import com.intellij.xdebugger.impl.evaluate.childCoroutineScope import com.intellij.xdebugger.impl.evaluate.quick.XValueHint @@ -70,7 +73,23 @@ internal class XQuickEvaluateHandler : QuickEvaluateHandler() { LOG.error("invalid range: $range, text length = $textLength") return@async null } - if (Registry.`is`("debugger.valueLookupFrontendBackend") || FrontendApplicationInfo.getFrontendType() is FrontendType.RemoteDev) { + if (Registry.`is`("debugger.valueLookupFrontendBackend")) { + // TODO: use proper coroutine scope + val evaluatorScope = editor.childCoroutineScope("XQuickEvaluateHandler#evaluator") + val evaluatorId = withKernel { + XDebuggerValueLookupHintsRemoteApi.getInstance().createHintEvaluator(projectId) + } ?: return@async null + val frontendEvaluator = FrontendXDebuggerEvaluator(evaluatorScope, evaluatorId) + // TODO: provider proper editorsProvider + val editorsProvider = object : XDebuggerEditorsProvider() { + override fun getFileType(): FileType { + return FileTypes.PLAIN_TEXT + } + } + // TODO: support passing session: basically valueMarkers and currentPosition + XValueHint(project, editorsProvider, editor, point, type, expressionInfo, frontendEvaluator, false) + } + else if (FrontendApplicationInfo.getFrontendType() is FrontendType.RemoteDev) { RemoteValueHint(project, projectId, editor, point, type, offset, expressionInfo, fromPlugins = false) } else { diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/quick/XValueHint.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/quick/XValueHint.java index 241df4d9f0d0..c14d8224965c 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/quick/XValueHint.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/quick/XValueHint.java @@ -45,6 +45,7 @@ import com.intellij.xdebugger.impl.ui.tree.nodes.*; import com.intellij.xdebugger.impl.ui.tree.nodes.XEvaluationCallbackBase; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodePresentationConfigurator; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -79,7 +80,8 @@ public class XValueHint extends AbstractValueHint { this(project, session.getDebugProcess().getEditorsProvider(), editor, point, type, expressionInfo, evaluator, session, fromKeyboard); } - protected XValueHint(@NotNull Project project, + @ApiStatus.Internal + public XValueHint(@NotNull Project project, @NotNull XDebuggerEditorsProvider editorsProvider, @NotNull Editor editor, @NotNull Point point, diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerEvaluatorApi.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerEvaluatorApi.kt new file mode 100644 index 000000000000..61326cff7e48 --- /dev/null +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerEvaluatorApi.kt @@ -0,0 +1,43 @@ +// 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.xdebugger.impl.rpc + +import com.intellij.platform.kernel.withKernel +import com.intellij.platform.rpc.RemoteApiProviderService +import com.jetbrains.rhizomedb.EID +import fleet.rpc.RemoteApi +import fleet.rpc.Rpc +import fleet.rpc.remoteApiDescriptor +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.Serializable +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +@Rpc +interface XDebuggerEvaluatorApi : RemoteApi { + suspend fun evaluate(evaluatorId: XDebuggerEvaluatorId, expression: String): Deferred? + + suspend fun computePresentation(xValueId: XValueId): Flow? + + companion object { + @JvmStatic + suspend fun getInstance(): XDebuggerEvaluatorApi { + return withKernel { + RemoteApiProviderService.resolve(remoteApiDescriptor()) + } + } + } +} + +@ApiStatus.Internal +@Serializable +data class XValueId(val eid: EID) + + +@ApiStatus.Internal +@Serializable +data class XDebuggerEvaluatorId(val eid: EID) + +@ApiStatus.Internal +@Serializable +data class XValuePresentation(val value: String) \ No newline at end of file diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerValueLookupHintsRemoteApi.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerValueLookupHintsRemoteApi.kt index 24d07cb73c68..d7705845776c 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerValueLookupHintsRemoteApi.kt +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/rpc/XDebuggerValueLookupHintsRemoteApi.kt @@ -24,6 +24,8 @@ interface XDebuggerValueLookupHintsRemoteApi : RemoteApi { suspend fun createHint(projectId: ProjectId, editorId: EditorId, offset: Int, hintType: ValueHintType, fromPlugins: Boolean): RemoteValueHintId? + suspend fun createHintEvaluator(projectId: ProjectId): XDebuggerEvaluatorId? + suspend fun showHint(hintId: RemoteValueHintId): Flow suspend fun removeHint(hintId: RemoteValueHintId, force: Boolean) diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValuePresentationUtil.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValuePresentationUtil.java index ff0e8eb5675d..4720c4ecaa34 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValuePresentationUtil.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValuePresentationUtil.java @@ -1,4 +1,4 @@ -// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// 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.xdebugger.impl.ui.tree.nodes; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; @@ -10,6 +10,7 @@ import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; import com.intellij.xdebugger.frame.presentation.XValuePresentation; import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -91,10 +92,11 @@ public final class XValuePresentationUtil { return renderer instanceof XValuePresentationTextExtractor; } - private static class XValuePresentationTextExtractor extends XValueTextRendererBase { + @ApiStatus.Internal + public static class XValuePresentationTextExtractor extends XValueTextRendererBase { private final StringBuilder myBuilder; - XValuePresentationTextExtractor() { + public XValuePresentationTextExtractor() { myBuilder = new StringBuilder(); }