mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[debugger-rd] IJPL-160146: Introduce a way to use XValueHint natively on frontend
GitOrigin-RevId: 8eb16d5f8777893188e64d71f98edeeb36e076e7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8c1c13a2bd
commit
1e56268803
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user