[java-rd] IJPL-176967: Rewrite XValueDescriptor, so it may be extended by plugins

GitOrigin-RevId: 9fd606e89cbf0680357a12c5159cc99ec3463914
This commit is contained in:
Nikolay Rykunov
2025-02-20 18:52:25 +00:00
committed by intellij-monorepo-bot
parent abf7dd20c2
commit 50996fd44e
7 changed files with 131 additions and 38 deletions
@@ -2,11 +2,10 @@
package com.intellij.java.debugger.impl.frontend.actions
import com.intellij.java.debugger.impl.shared.actions.ViewTextActionBase
import com.intellij.java.debugger.impl.shared.engine.JAVA_VALUE_KIND
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification
import com.intellij.platform.debugger.impl.frontend.evaluate.quick.FrontendXValue
import com.intellij.xdebugger.frame.XValueType
import com.intellij.xdebugger.impl.actions.areFrontendDebuggerActionsEnabled
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
@@ -27,9 +26,9 @@ private class FrontendViewTextAction : ViewTextActionBase(), ActionRemoteBehavio
override fun getStringNode(e: AnActionEvent): XValueNodeImpl? {
val node = XDebuggerTreeActionBase.getSelectedNodes(e.dataContext).singleOrNull() ?: return null
val xValue = node.valueContainer as? FrontendXValue ?: return null
val descriptor = xValue.descriptor ?: return null
val descriptor = (xValue.descriptor as? JavaValueDescriptor) ?: return null
if (xValue.modifier != null && descriptor.kind == JAVA_VALUE_KIND && descriptor.type is XValueType.StringType) {
if (xValue.modifier != null && descriptor.isString) {
return node
}
@@ -22,6 +22,7 @@ import com.intellij.debugger.ui.impl.watch.*;
import com.intellij.debugger.ui.tree.*;
import com.intellij.debugger.ui.tree.render.*;
import com.intellij.debugger.ui.tree.render.Renderer;
import com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptor;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -58,7 +59,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import static com.intellij.java.debugger.impl.shared.engine.XValueTypeKt.JAVA_VALUE_KIND;
import static com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptorKt.JAVA_VALUE_KIND;
public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XValueTextProvider,
PinToTopParentValue, PinToTopMemberValue {
@@ -513,14 +514,9 @@ public class JavaValue extends XNamedValue implements NodeDescriptorProvider, XV
@Override
public @Nullable CompletableFuture<XValueDescriptor> getXValueDescriptorAsync() {
return myValueDescriptor.getInitFuture().thenApply(ignored -> {
XValueType type;
if (myValueDescriptor.isString()) {
type = XValueType.StringType.INSTANCE;
}
else {
type = XValueType.Unknown.INSTANCE;
}
return new XValueDescriptor(JAVA_VALUE_KIND, type);
return new JavaValueDescriptor(
myValueDescriptor.isString()
);
});
}
@@ -4,5 +4,7 @@
<applicationSettings service="com.intellij.debugger.settings.DebuggerSettings"/>
<xdebugger.hotSwapUiExtension implementation="com.intellij.debugger.impl.hotswap.JvmHotSwapUiExtension"/>
<rdct.remoteSettingProvider implementation="com.intellij.debugger.settings.JavaDebuggerSettingsProvider"/>
<xdebugger.xValueCustomDescriptorSerializerProvider
implementation="com.intellij.java.debugger.impl.shared.engine.JavaValueDescriptorSerializerProvider"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,26 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.debugger.impl.shared.engine
import com.intellij.xdebugger.frame.XValueCustomDescriptorSerializerProvider
import com.intellij.xdebugger.frame.XValueDescriptor
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.subclass
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
const val JAVA_VALUE_KIND: String = "JavaValue"
@ApiStatus.Internal
@Serializable
class JavaValueDescriptor(
val isString: Boolean,
) : XValueDescriptor {
override val kind: String = JAVA_VALUE_KIND
}
private class JavaValueDescriptorSerializerProvider : XValueCustomDescriptorSerializerProvider {
override fun registerSerializer(builder: PolymorphicModuleBuilder<XValueDescriptor>) {
builder.subclass(JavaValueDescriptor::class)
}
}
@@ -1,7 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.debugger.impl.shared.engine
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
const val JAVA_VALUE_KIND: String = "JavaValue"
@@ -1,16 +1,40 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.xdebugger.frame
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.ExtensionPointAdapter
import com.intellij.openapi.extensions.ExtensionPointName
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import org.jetbrains.annotations.ApiStatus
/**
* Provides additional information about XValue which can be used by UI and actions.
* Provides additional information about XValue which can be used by UI and actions on the Frontend.
*
* Internal plugins may extend this interface providing custom implementation, but they should be serializable.
* Also, it is required to provide serializer for the implementation using
* [com.intellij.xdebugger.frame.XValueCustomDescriptorSerializerProvider].
* Otherwise, the default implementation will be used during a serialization/deserialization process.
*
*
* @see createXValueDescriptor
* @see XValueCustomDescriptorSerializerProvider
*/
@ApiStatus.Internal
@Serializable
data class XValueDescriptor(
@Serializable(with = XValueDescriptorSerializer::class)
interface XValueDescriptor {
/**
* [kind] is used to differentiate various implementations of XValue by their type instead of using `instanceOf` on [XValue].
* Examples of possible kinds: "JavaValue", "PhpValue", "RubyValue"
@@ -19,25 +43,75 @@ data class XValueDescriptor(
* which makes `instanceOf` comparisons of XValue impossible, this property provides a way to
* differentiate between various types of XValue on the frontend.
*/
val kind: String,
val kind: String
}
/**
* [XValue] type (see instances of the [XValueType]).
* Typically, the client may use the type to check the availability of some action or show XValue differently in UI.
*/
val type: XValueType,
)
// TODO: should be called just XValueDescriptor, but it won't be available in Java then
@ApiStatus.Internal
fun createXValueDescriptor(kind: String): XValueDescriptor {
return XValueDescriptorImpl(kind)
}
@ApiStatus.Internal
interface XValueCustomDescriptorSerializerProvider {
companion object {
internal val EP_NAME = ExtensionPointName<XValueCustomDescriptorSerializerProvider>("com.intellij.xdebugger.xValueCustomDescriptorSerializerProvider")
}
fun registerSerializer(builder: PolymorphicModuleBuilder<XValueDescriptor>)
}
@Serializable
sealed interface XValueType {
// TODO: add other types of XValue
@Serializable
data object StringType : XValueType
private data class XValueDescriptorImpl(override val kind: String) : XValueDescriptor
@Serializable
data class Other(val rawType: String) : XValueType
@Serializable
data object Unknown : XValueType
private object XValueDescriptorSerializer : KSerializer<XValueDescriptor> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun serialize(encoder: Encoder, value: XValueDescriptor) {
val json = service<XValueDescriptorJsonProvider>().json
val element = json.encodeToJsonElement(value)
encoder.encodeSerializableValue(JsonElement.serializer(), element)
}
override fun deserialize(decoder: Decoder): XValueDescriptor {
val json = service<XValueDescriptorJsonProvider>().json
val element = decoder.decodeSerializableValue(JsonElement.serializer())
return json.decodeFromJsonElement(element)
}
}
@Service
private class XValueDescriptorJsonProvider(cs: CoroutineScope) {
@Volatile
var json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
init {
updateJson()
XValueCustomDescriptorSerializerProvider.EP_NAME.addExtensionPointListener(cs, object : ExtensionPointAdapter<XValueCustomDescriptorSerializerProvider>() {
override fun extensionListChanged() {
updateJson()
}
})
}
private fun updateJson() {
json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
serializersModule = SerializersModule {
polymorphic(XValueDescriptor::class) {
for (provider in XValueCustomDescriptorSerializerProvider.EP_NAME.extensionList) {
provider.registerSerializer(this)
}
defaultDeserializer {
XValueDescriptorImpl.serializer()
}
}
}
}
}
}
@@ -28,6 +28,9 @@
<extensionPoint name="xdebugger.controlExceptionBreakpointSupport" interface="com.intellij.xdebugger.codeinsight.ControlExceptionBreakpointSupport" dynamic="true"/>
<extensionPoint name="xdebugger.hotSwapUiExtension" interface="com.intellij.xdebugger.impl.hotswap.HotSwapUiExtension" dynamic="true"/>
<extensionPoint name="xdebugger.xValueCustomDescriptorSerializerProvider"
interface="com.intellij.xdebugger.frame.XValueCustomDescriptorSerializerProvider" dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">