[debugger-rd] IJPL-160146: Introduce a way to use XValueHint natively on frontend

GitOrigin-RevId: 8eb16d5f8777893188e64d71f98edeeb36e076e7
This commit is contained in:
Nikolay Rykunov
2024-10-02 19:07:30 +02:00
committed by intellij-monorepo-bot
parent 8c1c13a2bd
commit 1e56268803
11 changed files with 282 additions and 13 deletions

View File

@@ -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<XValueId>? = withKernel {
val evaluatorEntity = entity(evaluatorId.eid) as? XDebuggerEvaluatorEntity ?: return@withKernel null
val evaluator = evaluatorEntity.evaluator
val evaluationResult = CompletableDeferred<XValue>()
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<XValuePresentation>? = withKernel {
val hintEntity = entity(xValueId.eid) as? LocalHintXValueEntity ?: return@withKernel null
val presentations = MutableSharedFlow<XValuePresentation>(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)
}
}
}
}

View File

@@ -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<XDebuggerValueLookupHintsRemoteApi>()) {
BackendXDebuggerValueLookupHintsRemoteApi()
}
remoteApi(remoteApiDescriptor<XDebuggerEvaluatorApi>()) {
BackendXDebuggerEvaluatorApi()
}
}
}

View File

@@ -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,

View File

@@ -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<EntityType<*>> {
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<ProjectEntity>("project")
val Hint = requiredTransient<AbstractValueHint>("hint")
}
}
internal data class LocalHintXDebuggerEvaluatorEntity(override val eid: EID) : XDebuggerEvaluatorEntity {
companion object : EntityType<LocalHintXDebuggerEvaluatorEntity>(
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>(XDebuggerEvaluatorEntity::class) {
val Project = requiredRef<ProjectEntity>("project")
val Evaluator = requiredTransient<XDebuggerEvaluator>("evaluator")
}
}
internal data class LocalHintXValueEntity(override val eid: EID) : Entity {
val projectEntity by Project
val xValue by XValue
companion object : EntityType<LocalHintXValueEntity>(
LocalHintXValueEntity::class.java.name,
"com.intellij",
::LocalHintXValueEntity
) {
val Project = requiredRef<ProjectEntity>("project")
val XValue = requiredTransient<XValue>("xValue")
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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<Unit> {
suspend fun evaluate(evaluatorId: XDebuggerEvaluatorId, expression: String): Deferred<XValueId>?
suspend fun computePresentation(xValueId: XValueId): Flow<XValuePresentation>?
companion object {
@JvmStatic
suspend fun getInstance(): XDebuggerEvaluatorApi {
return withKernel {
RemoteApiProviderService.resolve(remoteApiDescriptor<XDebuggerEvaluatorApi>())
}
}
}
}
@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)

View File

@@ -24,6 +24,8 @@ interface XDebuggerValueLookupHintsRemoteApi : RemoteApi<Unit> {
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<Unit>
suspend fun removeHint(hintId: RemoteValueHintId, force: Boolean)

View File

@@ -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();
}